/// axali godis Chasmisas, daakomente:
/// xazi:
/// //LiquidCrystal lcd(12,255,11,10,9,8,7,6); // (RS,RW,En1,En2,D4,D5,D6,D7) 255 if RW is connected to GND and not controlled by the interface.
/// da es "lcd.begin(nColumns,nRows); " gadaakeTe amad: "lcd.begin();""
// include 4x40 LED driver
#include <LiquidCrystal.h>
#include <math.h>
//#include <SoftwareSerial.h>
/////////////////////////// Merge two LCD's
class LiquidCrystal_40x4 {
private:
LiquidCrystal &lcd1, &lcd2; // References to both LCDs
int cursorCol, cursorRow; // Track the cursor position
unsigned long lastUpdate; // Time tracking for LCD synchronization
const int updateInterval = 500; // Update both LCDs every 500ms
public:
LiquidCrystal_40x4(LiquidCrystal &l1, LiquidCrystal &l2)
: lcd1(l1), lcd2(l2), cursorCol(0), cursorRow(0), lastUpdate(0) {}
void begin() {
lcd1.begin(20, 4);
lcd2.begin(20, 4);
clear();
}
void clear() {
lcd1.clear();
lcd2.clear();
cursorCol = 0;
cursorRow = 0;
}
void setCursor(int col, int row) {
cursorCol = col;
cursorRow = row;
}
void print(const char* message) {
int col = cursorCol;
int row = cursorRow;
for (int i = 0; message[i] != '\0'; i++) {
if (col >= 40) { // Move to the next row if out of bounds
col = 0;
row++;
}
if (row >= 4) break; // Stop if beyond 4 rows
if (col < 20) {
lcd1.setCursor(col, row);
lcd1.print(message[i]);
} else {
lcd2.setCursor(col - 20, row);
lcd2.print(message[i]);
}
col++;
}
// Update cursor position for future prints
cursorCol = col;
cursorRow = row;
}
void print(String message) { print(message.c_str()); } // Overload for String
void createChar(uint8_t location, uint8_t charmap[]) {
lcd1.createChar(location, charmap);
lcd2.createChar(location, charmap);
}
void write(uint8_t value) {
if (cursorCol < 20) {
lcd1.setCursor(cursorCol, cursorRow);
lcd1.write(value);
} else {
lcd2.setCursor(cursorCol - 20, cursorRow);
lcd2.write(value);
}
cursorCol++; // Move cursor right
}
void print(unsigned int value, int base) {
print(String(value, base));
}
void update() {
unsigned long currentMillis = millis();
if (currentMillis - lastUpdate >= updateInterval) {
lastUpdate = currentMillis;
}
}
};
LiquidCrystal lcd1(12, 11, 10, 9, 8, 7);
//LiquidCrystal lcd2(22, 23, 24, 25, 26, 27);
LiquidCrystal lcd2(48, 49, 50, 51, 52, 53);
LiquidCrystal_40x4 lcd(lcd1, lcd2);
///////////////////////////End of Merge two LCD's
/////////////////////////// Serial RS232
//SoftwareSerial CATSerial(14, 15); // RX=14, TX=15 on Mega
char responsePC[40];
char responseFA[40];
byte index = 0;
int INpower = 0;
/////////////////////////// END of Serial RS232
uint8_t nRows = 4; //number of rows on LCD
uint8_t nColumns =40; //number of columns
// Modify the pin number below to meet your board
//Analog pins
const int IN_FWD = A0; // analog input for left channel
const int IN_REFL = A1; // analog input for right channel
#define IN_NTC A2 // analog input for right channel
#define IN_50vv A3 // analog input for 50v voltage
#define IN_50vI A4 // analog input for 50v current
// Digital pins
#define IN_reset 40 // input for recover/SELFTEST switch detect - momentary pulling to GND (0 = recover/SELFTEST)
#define IN_PTT 41 // input for PTT detect (0 = keyed)
#define IN_OPERATE 2 // input for operate detect (1 = OPERATE)
#define supply_50v 3 // output for 50v supply control (1 = 50v ON)
#define Buzzer 4 // output for Buzzer control (1 = ON)
#define FANcontrol 5 // output for FAN control (PWM !)
//(6,7,8,9,10,11,12 - LCD 40x4)
#define PTT_block 13// output for PTT block control (no alarm = 0, FAULT = 1...n)
#define LPF_Minus 22
#define LPF_Plus 23
/////////////////////////////////////////// LPF Relays
const int LPFrelayPins[] = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33}; // Relay pins connected to digital pins 2 to 9
int activeLPFRelay = -1; // Variable to store the current active relay
// Band160 = 24; Band80 = 25; Band40 = 26; Band30 = 27; Band20 = 28; Band17 = 29;
// Band15 = 30; Band12 = 31; Band10 = 32; Band6 = 33;
////////////////////////////////////////// End of LPF Relays
////////////////////////////////////////////////// Data from radio
const int bandCodePins[] = {34, 35, 36, 37};
// Pin definitions for binary input
// BandCode: 0001 -> LPF Band 160
// BandCode: 0010 -> LPF Band 80
// BandCode: 0011 -> LPF Band 40
// BandCode: 0100 -> LPF Band 30
// BandCode: 0101 -> LPF Band 20
// BandCode: 0110 -> LPF Band 17
// BandCode: 0111 -> LPF Band 15
// BandCode: 1000 -> LPF Band 12
// BandCode: 1001 -> LPF Band 10
// BandCode: 1010 -> LPF Band 6
////////////////////////////////////////////////// End of Data from radio
const String bandNames[] = {"160m", "80m", "40m", "30m", "20m", "17m", "15m", "12m", "10m", "6m"};
String band = ""; // Global variable to hold band info
#define LPF_Jumper 38
#define NUM_BANDS 10
// Button state tracking
bool lastMinusState = HIGH;
bool lastPlusState = HIGH;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;
int bandCode = 0;
static unsigned long buttonPressTime = 0;
const unsigned long longPressThreshold = 500; // Hold for 500ms = long press
const unsigned long repeatDelay = 150; // Fast switch delay
static bool repeating = false;
///////////////////////////////////////
#define FAN_P 39 // Output fan off high
// Other minor configurable value
#define T_REFRESH1 0.1 // msec bargraph refresh rate
#define T_REFRESH2 800 // msec refresh rate other variables
#define T_PEAKHOLD 1000 // msec peak hold time before return
#define T_pepHOLD 1000 // msec pep hold time before return
// FAN speeds (0 ... 255) PWM driven. Adjust according to your fan type !
#define fan_vy_slow (20) // Very slow, idle speed
#define fan_slow (30) // Slow
#define fan_med_lo (40) // Medium speeds, low
#define fan_med_hi (50) // Medium Hi, start of PID regulation till 255 = max !
// Calibration factors
float Vreference = 4.97;
float slopeF = 38.0389; // Slope of the AD8307 log output (Default = 40)
float interceptF = 35.4292; // 0V intercept point (Default = 44)
float slopeR = 39.292; // Slope of the AD8307 log output (Default = 40)
float interceptR = 38.391; // 0V intercept point (Default = 44)
float adcValueF = 0;
float voltageF = 0;
double powerdBmF = 0;
float adcValueR = 0;
float voltageR = 0;
double powerdBmR = 0;
double SWR = 1.0;
double refl_coeff = 0;
unsigned long pow_fwd = 0; // power forward (watts)
unsigned long pow_ref = 0; // power forward (watts)
unsigned long pow_Drive = 0; // Input power (watts)
unsigned int pow_fwd_max = 0; // power forward max (for peak hold)
unsigned int pow_ref_max = 0; // power reflected max (for peak hold)
int SWRDis = 0; // power calculation for showing in display
int eta = 0;
// local variable
byte fill[6]={ 0x20,0x00,0x01,0x02,0x03,0xFF }; // character used to fill (0=empty 5=full)
byte peak[7]={ 0x20,0x00,0x04,0x05,0x06,0x07,0x20 }; // character used to peak indicator
int lmax[5]; // level max memory
int dly[5]; // delay & speed for peak return
long lastT1=0; // update display timer1
long lastT2=0; // update display timer2
long lastTpep = 0; // update PEP display
long lastTempTime = 0; // update last temp readout for fan control
long LastTXtime = 0; // timestamp when last transmission ended (for FAN control)
long HiPowerTime = 0; // timestamp Hig Power - for EME/Digital mode conditions
long HiAmp50Time = 0; // timestamp high current value on 50v power supply
int smooth_lev[2] = {0, 0}; // One for each row
int anF = 0; // analog read forward power
int anR = 0; // analog read reflected power
int volt_50 = 0; // 50v volt supply - voltage (in 0,1 volt)
int amp_50 = 0; // 50v volt supply - current (in 0,1 Amp)
int amp_50_max = 0; // 50v volt supply - max current (in 0,1 Amp)
int temp = 0; // temperature (in 1 °C)
int lasttemp = 0; // last temperature for fan control
byte PTT = 0; // 0 = RX, 1 = TX
byte wasPTT = 0; // former RX/TX status byte : if 1 = was in TX mode
byte TestMode = 0; // 0 = normal mode, 1 = TestMode (PA will 'operate' with no +50v). Testmode is
// initiated by depressing 'RESET4 button upon startup of SSPA
int Operate = 0; // When operate = 0 , PA in stby mode
int FAN = (0); // Fan control (PWM) - 255 = full speed
byte HiTemp = (0); // Temperature passing above PID start temp
byte FAULT = 0; // when FAULT = 0 : NO FAULT
// when FAULT = 1 : Overcurrent
// when FAULT = 2 : voltage error 50v
// when FAULT = 5 : power out error
// when FAULT = 6 : SWR error
// when FAULT = 7 : NTC sensor read error
// when FAULT = 8 : Temperature error
// when FAULT = 9 : 50v idle current error
// when FAULT = 10: W6PQL control PCB SWR exceed fault
// when FAULT = 11: Power supply voltage too high in DIGI mode(= poor efficiency)
// when FAULT = 12: PA power output too high in DIGI mode
byte FirstLoop = (1); // First loop detect
byte SelfTest = (1); // 1 to force selftest
byte HiAmp50 = (0); // Flag to detect long lasting current peaks on 50v supply
byte DIGImode = (0); // 0 = normal, 1 = EME/digtal mode conditions (no SSB but CW/JT65/FT-8/FSK ....)
//LiquidCrystal lcd(12,255,11,10,9,8,7,6); // (RS,RW,En1,En2,D4,D5,D6,D7) 255 if RW is connected to GND and not controlled by the interface.
//=====for a 4x40 LCD with 2 HD44780 type chips and 18 pin interface in 2 rows of 9;
// LCD Nano Signal
// 18 Gnd Backlight white on blue 4x LED draws 40 mAmps
// 17 +5V Backligt + through external resistor 15 Ohm.
// 16 NC not used
// 15 10 En2 -- enable the 2nd HD44780 chip which controls the bottom 2 rows of the display
// 14 +5V supply logic
// 13 Gnd logic
// 12 Wiper of contrast resistor (22k between +5v and Gnd)
// 11 12 RS
// 10 Connect to Gnd
// 9 11 En1 -- enable the 1st HD44780 which controls the top 2 rows
// 5-8 Data 0-3: not used in 4 bit modes
// 1-4 09-06 Data 4-7: LCD DB7 to ARDUINO 06, DB6 to 07, DB5 to 08, DB4 to 09
/////////////////// DRAW BAR //////////////////////////
void bar(int row, int lev)
{
// === Fast rise / slow decay logic ===
if (lev > smooth_lev[row]) {
smooth_lev[row] = lev; // Fast rise
} else {
smooth_lev[row] = smooth_lev[row] + (lev - smooth_lev[row]) / 10; // Slow fall
}
lcd.setCursor(3, row);
lcd.write(' '); // Filler space (optional)
for (int i = 1; i < 30; i++)
{
int f = constrain(smooth_lev[row] - i * 5, 0, 5); // Use smoothed level
int p = constrain(lmax[row] - i * 5, 0, 6); // Peak logic stays unchanged
if (f)
lcd.write(fill[f]);
else
lcd.write(peak[p]);
}
// === Peak hold logic (unchanged) ===
if (lev > lmax[row])
{
lmax[row] = lev;
dly[row] = -(T_PEAKHOLD) / T_REFRESH1;
}
else
{
if (dly[row] > 0)
lmax[row] -= dly[row];
if (lmax[row] < 0)
lmax[row] = 0;
else
dly[row]++;
}
}
byte block[8][8]=
{
{ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10 }, // define character for fill the bar
{ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18 },
{ 0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C },
{ 0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E },
{ 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08 }, // define character for peak level
{ 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04 },
{ 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02 },
{ 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01 },
};
///////////////// PRINT TEMPLATE ///////////////////
void printtemplate () {
lcd.setCursor( 0,0 );
lcd.print( "FWD 0w");
lcd.setCursor( 0,1 );
lcd.print( "REF 0w");
lcd.setCursor( 6,2 );
lcd.print( "Vd=--.-");
lcd.setCursor( 6,3 );
lcd.print( "Id=--.-");
lcd.setCursor( 16,2 );
lcd.print( "LPF ");
lcd.setCursor( 16,3 );
lcd.print( "BAND ");
lcd.setCursor( 26,2 );
lcd.print( "t--");
lcd.print((char)223); //degree symbol
//lcd.print((char)165);
lcd.setCursor( 0,2 );
lcd.print( "swr");
lcd.setCursor( 0,3 );
lcd.print( "-.-");
lcd.setCursor( 36,2 );
lcd.print( "stby");
lcd.setCursor( 4,2 );
lcd.write( peak[3] );
lcd.setCursor( 4,3 );
lcd.write( peak[3] );
lcd.setCursor( 14,2 );
lcd.write( peak[3] );
lcd.setCursor( 14,3 );
lcd.write( peak[3] );
lcd.setCursor( 24,2 );
lcd.write( peak[3] );
lcd.setCursor( 24,3 );
lcd.write( peak[3] );
lcd.setCursor( 30,2 );
lcd.write( peak[3] );
lcd.setCursor( 30,3 );
lcd.write( peak[3] );
lcd.setCursor( 34,2 );
lcd.write( peak[3] );
lcd.setCursor( 34,3 );
lcd.write( peak[3] );
}
/////////// This function will calculate temperature from 10k NTC readout /////////////
double Thermister(int RawADC) {
double Temp;
Temp = log(((10240000/RawADC) - 10000));
Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp ))* Temp );
Temp = Temp - 273.15; // Convert Kelvin to Celcius
//Temp = 22; //remove after some time
// Temp = (Temp * 9.0)/ 5.0 + 32.0; // Convert Celcius to Fahrenheit
return Temp;
}
void activateLPFRelay(int relayIndex);
////////////////////////////////////////////////////////////////
/////////////////////////// SETUP //////////////////////
////////////////////////////////////////////////////////////////
void setup() {
//CATSerial.begin(4800); // CAT baud rate for FTDX10
/// Prepare I/O pins
pinMode(IN_PTT, INPUT);
pinMode(IN_OPERATE, INPUT);
pinMode(IN_reset, INPUT);
pinMode(FAN_P, OUTPUT);
pinMode(supply_50v, OUTPUT);
digitalWrite(supply_50v, LOW); // We do not allow +50v yet
pinMode(Buzzer, OUTPUT);
digitalWrite(Buzzer,0);
analogWrite(FANcontrol, FAN);
pinMode(PTT_block, OUTPUT);
digitalWrite(PTT_block,HIGH); // we force RX mode via W6PQL board till all is OK !
////////////////////////////LPF setup
pinMode(LPF_Jumper, INPUT);
pinMode(LPF_Minus, INPUT);
pinMode(LPF_Plus, INPUT);
for (int i = 0; i < NUM_BANDS; i++) {
pinMode(LPFrelayPins[i], OUTPUT);
digitalWrite(LPFrelayPins[i], LOW);
}
for (int i = 0; i < 4; i++) {
pinMode(bandCodePins[i], INPUT);
}
if (digitalRead(LPF_Jumper) == LOW) { // Manual Mode at startup
activeLPFRelay = 1; // Start with LPF for Band 80m
updateLPFRelays();
}
/////////////// END of LPF setup
lcd.begin();
for( int i=0 ; i<8 ; i++ )
lcd.createChar( i,block[i] );
lcd.clear();
lcd.setCursor(5,0 );
lcd.print ("1 . 2 5 k W V H F S S P A ");
lcd.setCursor(5,2 );
lcd.print (" By 4L7ZS 4L4CR 4l0VE ");
lcd.setCursor(5,3 );
lcd.print (" 03/2025 - V1 ");
delay (2000);
lcd.clear();
delay (500);
printtemplate();
lastT2 = millis(); // set T2 display refresh
}
///////////////////////////////////////////////////////////
/////////////////////////// LOOP //////////////////////////
///////////////////////////////////////////////////////////
////////////////////// LOOP
void loop() {
//////////////////////////////////////////// LPF
bool isManual = digitalRead(LPF_Jumper) == LOW;
static bool wasManual = false; // tracks last mode to detect mode switch
if (isManual && FAULT == 0) {
if (!wasManual) {
lastMinusState = digitalRead(LPF_Minus);
lastPlusState = digitalRead(LPF_Plus);
wasManual = true;
}
bool currentMinusState = digitalRead(LPF_Minus);
bool currentPlusState = digitalRead(LPF_Plus);
unsigned long now = millis();
// === MINUS Button ===
if (currentMinusState == LOW) {
if (lastMinusState == HIGH) {
buttonPressTime = now; // New press
repeating = false;
} else if (!repeating && (now - buttonPressTime) > longPressThreshold) {
activeLPFRelay = (activeLPFRelay - 1 + NUM_BANDS) % NUM_BANDS;
updateLPFRelays();
buttonPressTime = now;
repeating = true;
} else if (repeating && (now - buttonPressTime) > repeatDelay) {
activeLPFRelay = (activeLPFRelay - 1 + NUM_BANDS) % NUM_BANDS;
updateLPFRelays();
buttonPressTime = now;
}
}
// === PLUS Button ===
if (currentPlusState == LOW) {
if (lastPlusState == HIGH) {
buttonPressTime = now; // New press
repeating = false;
} else if (!repeating && (now - buttonPressTime) > longPressThreshold) {
activeLPFRelay = (activeLPFRelay + 1) % NUM_BANDS;
updateLPFRelays();
buttonPressTime = now;
repeating = true;
} else if (repeating && (now - buttonPressTime) > repeatDelay) {
activeLPFRelay = (activeLPFRelay + 1) % NUM_BANDS;
updateLPFRelays();
buttonPressTime = now;
}
}
// Reset states when buttons released
if (currentMinusState == HIGH && lastMinusState == LOW) {
if (!repeating) {
activeLPFRelay = (activeLPFRelay - 1 + NUM_BANDS) % NUM_BANDS;
updateLPFRelays();
}
}
if (currentPlusState == HIGH && lastPlusState == LOW) {
if (!repeating) {
activeLPFRelay = (activeLPFRelay + 1) % NUM_BANDS;
updateLPFRelays();
}
}
lastMinusState = currentMinusState;
lastPlusState = currentPlusState;
} else if (!isManual) {
// === Auto Mode ===
wasManual = false; // reset transition flag
bandCode = 0;
for (int i = 0; i < 4; i++) {
bandCode |= (digitalRead(bandCodePins[i]) << i);
}
if (bandCode >= 1 && bandCode <= NUM_BANDS) {
if (activeLPFRelay != (bandCode - 1)) {
activeLPFRelay = bandCode - 1;
updateLPFRelays();
}
FAULT = 0;
} else {
FAULT = 13;
fault();
}
}
//////////////////////////////////////////// END of LPF
// Check if TESTMODE required. Testmode is initiated by depressing the RESET button upon powering up.
// In testmode, no 50v supply is applied to PA pallet, and no 50v is required to go into 'OPERATE' and 'TX' mode
// exiting TESTMODE is only possible by powering up again the power amplifier.
if (((digitalRead(IN_reset) == LOW) or (TestMode == 1))and (FirstLoop == 1) ) {
TestMode = 1;
lcd.setCursor(10,0 );
lcd.print ("T E S T M O D E !");
lcd.setCursor( 7,1 );
lcd.print ("(N O + 5 0 v T O P A)");
digitalWrite(Buzzer,HIGH);
delay (200);
digitalWrite(Buzzer,LOW);
lcd.setCursor(10,0 );
lcd.print (" ");
lcd.setCursor( 7,1 );
lcd.print (" ");
delay (200);
digitalWrite(Buzzer,HIGH);
lcd.setCursor(10,0 );
lcd.print ("T E S T M O D E !");
lcd.setCursor( 7,1 );
lcd.print ("(N O + 5 0 v T O P A)");
delay (200);
digitalWrite(Buzzer,LOW);
lcd.setCursor(10,0 );
lcd.print (" ");
lcd.setCursor( 7,1 );
lcd.print (" ");
delay (200);
digitalWrite(Buzzer,HIGH);
lcd.setCursor(10,0 );
lcd.print ("T E S T M O D E !");
lcd.setCursor( 7,1 );
lcd.print ("(N O + 5 0 v T O P A)");
delay (200);
digitalWrite(Buzzer,LOW);
delay (500);
printtemplate();
}
// update OPERATE / STBY mode
if (digitalRead(IN_OPERATE) == HIGH) Operate = 1;
else Operate = 0;
// update PTT status
if (digitalRead(IN_PTT) == HIGH) PTT = 1;
else PTT = 0;
// Check if no PTT & operate mode at startup of our PA
if ((Operate == 1) and (FirstLoop == 1) and (PTT == 1)) {
digitalWrite(Buzzer,HIGH);
lcd.setCursor(5,0 );
lcd.print ("SET TRANSCEIVER IN RX PLEASE !");
delay (500);
digitalWrite(Buzzer,LOW);
printtemplate();
delay (500);
loop();
}
/////////////////////////////////////////////////////////////////////////////////////////
/////// perform EVERY LOOP some measurement we need for critical faults detection ///////
/////////////////////////////////////////////////////////////////////////////////////////
// Check input Power and Band
if (PTT == 1) {
if (readCAT()) {
printPower(responsePC);
printFrequencyAndBand(responseFA);
}
if (INpower > 60) {
FAULT = 14;
fault(); // Jump to FAULT handling
}
if ((band != bandNames[activeLPFRelay]) and (!isManual)){
FAULT = 15;
fault(); // Jump to FAULT handling
}
}
// END of Check input Power
// Check Current 50v
amp_50 = map (analogRead(IN_50vI), 0, 1023, 0, 2700); //90 Amp ATTOPILOT board - convert to 0,1 A
if (amp_50 < 31) amp_50 = amp_50 + (amp_50/4) ; // Arduino not linear at very low voltages ?
if (amp_50 < 51) amp_50 = amp_50 + (amp_50/7) ;
if (amp_50 < 101) amp_50 = amp_50 + (amp_50/10) ;
if (amp_50 < 150) amp_50 = amp_50 + (amp_50/20) ;
if (amp_50 > amp_50_max) amp_50_max = amp_50; // measure peak current every T1 to display max every T2
//Detect current peak (instantaneous, 'never exceed' value) :
if (amp_50 >= 400) { // Overcurrent ! peak with more than 40A !
FAULT = 1;
fault(); // Jump to FAULT handling
}
//Detect current of 'long lasting' peak of at least 100mS (overdrive of PA - protect LDMOS) :
if (amp_50 >= 400) { // more than 35A
if (HiAmp50 == 0) HiAmp50Time = millis(); // timestamp reading
HiAmp50 = 1;
}
else (HiAmp50 = 0);
if ((HiAmp50 == 1) and ((millis() - HiAmp50Time) > 100)) { // we have a peak !
HiAmp50 = 0;
FAULT = 1;
fault(); // Jump to FAULT handling
}
// Check Voltage 50v (48 - 55V)
volt_50 = map (analogRead(IN_50vv), 0, 1023, 0, 1137); //90 Amp ATTOPILOT board - convert to 0,1 V
if ((volt_50 >= 610) ){ // Overvoltage, always alarm !
FAULT = 2;
fault(); // Jump to FAULT handling
}
if ((volt_50 <= 40) and (Operate == 1) and (TestMode == 0) ){ // Undervoltage while in operate mode!
FAULT = 2;
fault(); // Jump to FAULT handling
}
// All critical parameters checked, if we are in SELFTEST mode on startup we can now release PTT block and allow +50v supply PA
if ((SelfTest == 1) ) {
DIGImode = (0); // Reset DIGImode condition
if (TestMode == 0) digitalWrite(supply_50v,HIGH); // +50v only when not in testmode
// Check current 50v, should be 0 !
amp_50 = map (analogRead(IN_50vI), 0, 1023, 0, 1710); //90 Amp ATTOPILOT board - convert to 0,1 A
if ((amp_50 > 1) and (TestMode == 0) ) { // more than 0.1 A idle current with no bias! Fault in pallet !
digitalWrite(supply_50v,LOW); // immediately switch off 50v !
FAULT = 9;
fault(); // Jump to FAULT handling
}
lcd.setCursor( 13,0 );
lcd.print ("SELFTEST = OK !");
if (FirstLoop == 1) analogWrite(FANcontrol, 120); // Fan test
// Morse code
// Oscar
digitalWrite(Buzzer,HIGH);
delay (240);
digitalWrite(Buzzer,LOW);
delay (80);
digitalWrite(Buzzer,HIGH);
delay (240);
digitalWrite(Buzzer,LOW);
delay (80);
digitalWrite(Buzzer,HIGH);
delay (240);
digitalWrite(Buzzer,LOW);
// space
analogWrite (FANcontrol,0);
delay (240);
// Kilo
digitalWrite(Buzzer,HIGH);
delay (240);
digitalWrite(Buzzer,LOW);
delay (80);
digitalWrite(Buzzer,HIGH);
delay (80);
digitalWrite(Buzzer,LOW);
delay (80);
digitalWrite(Buzzer,HIGH);
delay (240);
digitalWrite(Buzzer,LOW);
delay (500);
// end CW tune
// NOW PA READY TO GO !
digitalWrite(PTT_block,LOW);
SelfTest = (0);
LastTXtime = millis() - 35000; // avoid FAN running at startup
}
/////////////////////////////////////////////////////////////////
///// we must now update screen for timer T1 = short cycle //////
/////////////////////////////////////////////////////////////////
if (( millis()<lastT1 ) and (FirstLoop == 0)) return;
lastT1 = millis() + T_REFRESH1;
adcValueF = analogRead(IN_FWD);
voltageF = (adcValueF / 1023.0) * Vreference; // Convert ADC to V
powerdBmF = (slopeF*voltageF)-interceptF;
pow_fwd = pow(10, (powerdBmF - 30.0) / 10.0);
adcValueR = analogRead(IN_REFL);
voltageR = (adcValueR / 1023.0) * Vreference; // Convert ADC to V
powerdBmR = (slopeR*voltageR)-interceptR;
pow_ref = pow(10, (powerdBmR - 30.0) / 10.0);
// detect PA overdrive
if (pow_fwd > 2000) { // PA overdriven ! (limit for 1k8 version)
FAULT = 5;
fault(); // Jump to FAULT handling
}
// detect if SSB or DIGI condition (more than 14 seconds continuous CW/JT65/FT-8 ....)
if ((pow_fwd < 500)) { // less than 500w continuous
HiPowerTime = millis();
if (millis() - HiPowerTime > 30000) DIGImode = 0; // wait 30s to recover from EME mode
}
if ((pow_fwd > 500) and (millis() - HiPowerTime > 14000)) { // more than 500W continuous during 14s
DIGImode = (1);
}
if ((volt_50 > 490) and (DIGImode == 1)){ // Limit supply power to 48V for digital modes (= optimal efficiency)
FAULT = 11;
fault(); // Jump to FAULT handling
}
if ((pow_fwd > 1100) and (DIGImode == 1)){ // Limit output power for digital modes to 1kW
FAULT = 12;
fault(); // Jump to FAULT handling
}
// detect SWR error / load mismatch
refl_coeff = sqrt((double)pow_ref / pow_fwd);
SWR = abs((1+refl_coeff) / (1-refl_coeff));
if ((SWR > 2) and (pow_fwd > 50) and (Operate == 1)) { // only when forward power > 50w
FAULT = 6;
fault(); // Jump to FAULT handling
}
// Detect if is in SWR error
if ((pow_ref > 50) and (pow_fwd < 50) and (Operate == 1)) { // When W6PQL trips on SWR fault, REFL power steady approx 20w
FAULT = 10;
fault(); // Jump to FAULT handling
}
// bargraph display
anF = map( pow_fwd, 0, 1500, 0, 150); // 150 = 30 x 5 colums, full scale 1250w
anR = map( pow_ref, 0, 125, 0, 150); // 150 = 30 x 5 colums, full scale 125 w
bar(0, anF);
bar(1, anR);
// digital readout of power FWD & REFL
// update PEP¨meter ?
if (pow_fwd >= pow_fwd_max) { // we have a peak !
lastTpep = millis();
pow_fwd_max = pow_fwd;
pow_ref_max = pow_ref;
}
if (millis() > (lastTpep + T_pepHOLD)) { // clear the peak after hold time
pow_fwd_max = pow_fwd;
pow_ref_max = pow_ref;
}
lcd.setCursor( 34,0 ); // print forward power max
if (pow_fwd_max > 999) {
lcd.print( "1.");
if ((pow_fwd_max - 1000) < 100) lcd.print( "0");
if ((pow_fwd_max - 1000) < 10) lcd.print( "0");
lcd.print( (pow_fwd_max - 1000),DEC);
}
else {
if (pow_fwd_max < 1000) lcd.print( " ");
if (pow_fwd_max < 100) lcd.print( " ");
if (pow_fwd_max < 10) lcd.print( " ");
lcd.print(pow_fwd_max,DEC);
}
lcd.setCursor( 34,1 ); // print reflected power
if (pow_ref_max > 999) { // hopefully not the case
lcd.print( "1.");
if ((pow_ref_max - 1000) < 100) lcd.print( "0");
if ((pow_ref_max - 1000) < 10) lcd.print( "0");
lcd.print( (pow_ref_max - 1000),DEC);
}
else {
if (pow_ref_max < 1000) lcd.print( " ");
if (pow_ref_max < 100) lcd.print( " ");
if (pow_ref_max < 10) lcd.print( " ");
lcd.print(pow_ref_max, DEC);
}
// update ETA STATUS
lcd.setCursor(31, 2);
lcd.print( "ETA");
lcd.setCursor(31, 3);
eta = 100 * pow_fwd / ((0.01+amp_50/10) * (volt_50/10));
if (eta < 99 and eta > 0) {
lcd.print((eta), DEC);
lcd.setCursor(33, 3);
lcd.print("%");
}
else {
lcd.setCursor(31, 3);
lcd.print( " ");
}
// update OPER / STBY STATUS
if (Operate == 1){ // OPERATE
lcd.setCursor( 36,2 );
lcd.print( "OPER");
}
else { // STANDBY
lcd.setCursor( 36,2 );
lcd.print( "stby");
lcd.setCursor( 36,3 );
lcd.print( " "); // Clear TX / RX indicator
LastTXtime = millis() - 31000; // reset FAN timer
}
// update TX / RX STATUS
if (Operate == 1){
lcd.setCursor( 36,3 );
if (PTT == 1){
lcd.print( "-TX-");
wasPTT = 1;
}
if (PTT == 0){
lcd.print( "-RX-");
if (wasPTT == 1) { // previous state was TX !
wasPTT = 0;
LastTXtime = millis();
}
}
}
///////////////////////////////////////////////////////////////////////////
//////////// we must update screen for timer T2 = long cycle //////////////
///////////////////////////////////////////////////////////////////////////
if ((millis()-lastT2)>T_REFRESH2) {
lastT2 = millis();
// SWR calculation & display
SWRDis = (SWR * 10) + 0.5; // display SWR one figure after DP, round upwards
if (SWRDis < 10){ // SWR cannot be lower than 1.0
SWRDis = 10 ;
}
lcd.setCursor( 0,3 );
if (SWRDis >= 50) {
lcd.print(">5!");
}
if (pow_fwd < 5) {
lcd.print("-.-");
}
else if (SWRDis < 50){
lcd.print((SWRDis/10), DEC);
lcd.print(".");
lcd.print((SWRDis)%10, DEC);
}
// Temperature check & display
temp = ((Thermister(1023 - analogRead(IN_NTC))) + 0.5);
// if (temp >= 0) lcd.print(" "); // If temp positive, print space, else a '-' will show up
lcd.setCursor( 27,2 );
if ((temp < 0) or (temp > 99)){
lcd.print("??");
FAULT = 7; // NTC error !
fault();
}
else {
constrain (temp,0,99);
if ((temp)<10) lcd.print("0");
lcd.print(temp, DEC);
}
if (((temp >= 0) and (temp <= 10)) or (temp >= 65)) { // FAULT check
FAULT = 8; // temperature error, too cold or hot !
fault();
}
// + 50v SUPPLY Volt & Amp measurements display
lcd.setCursor( 9,2 ); // Volts
if (volt_50 > 999) volt_50 = 999; // smoke in the shack !
if (volt_50<100) lcd.print(" ");
lcd.print((volt_50/10), DEC);lcd.print(".");
lcd.print((volt_50)%10, DEC);
lcd.setCursor( 9,3 ); // Amps
if (amp_50_max > 999) amp_50_max = 999;
if (amp_50_max<100) lcd.print(" ");
lcd.print((amp_50_max/10), DEC);
lcd.print(".");
lcd.print((amp_50_max)%10, DEC);
amp_50_max = 0; // reset after display
//LPF LCD info
lcd.setCursor(20, 2);
lcd.print(" ");
lcd.setCursor(20, 2);
if (!isManual) {
lcd.print("AUT");
}
else{
lcd.print("MAN");
}
lcd.setCursor(20, 3);
lcd.print(" ");
lcd.setCursor(20, 3);
lcd.print(bandNames[activeLPFRelay]);
/////////////////////////////////////////////////////////////
////// Perform some less time related critical controls /////
/////////////////////////////////////////////////////////////
// FAN CONTROL
// start of routine condition, FAN not runnning
if (temp < 35) digitalWrite(FAN_P,HIGH);
if (temp > 38) digitalWrite(FAN_P,LOW);
if ((temp >= 35) and (temp < 40)) FAN = fan_vy_slow;
if ((temp >= 40) and (temp < 45)) FAN = fan_med_lo;
if ((temp >= 45) and (FirstLoop == 1)) FAN = 255; //Temp high at power up, immediately cool down !
if (temp < 45) HiTemp = 0;
if (FAULT == 7) FAN = 0; // we have a NTC error, stop FAN !
lcd.setCursor (26,3);
/* // 000 - 255 indication
if ((FAN) < 100) lcd.print("0");
if ((FAN) < 10) lcd.print("0");
lcd.print(FAN, DEC);
// end debug
*/
// if ((DIGImode == 1) or (temp >= 50)) FAN = 255; // Test !
// Now control FAN with proper value :
if ((DIGImode == 1) and (temp >= 55)) FAN = 255; // Bypass the PID routine, force maximum cooling immediately !
if (temp >= 60) FAN = 255; // Bypass the PID routine, force maximum cooling because limit is 65°
if (FAN == 0) lcd.print("fan");
if (FAN == fan_vy_slow) lcd.print("20%");
if (FAN == fan_slow) lcd.print("30%");
if (FAN == fan_med_lo) lcd.print("40%");
if (FAN >= fan_med_hi) {
lcd.print (map(FAN,fan_med_hi,255,50,99),DEC);
lcd.print("%");
}
analogWrite (FANcontrol,FAN);
// end FAN control routine
FirstLoop = 0; // first loop completely run
}
}
///////////////////// this is end of loop ///////////////////
///////////////////// FAULT CONDITION /////////////////////
// when FAULT = 0 : NO FAULT
// when FAULT = 1 : Overcurrent
// when FAULT = 2 : voltage error 50v
// when FAULT = 5 : power out error
// when FAULT = 6 : SWR error
// when FAULT = 7 : NTC read error
// when FAULT = 8 : Temperature error
// when FAULT = 9 : 50v idle current error
// when FAULT = 10: W6PQL control PCB SWR exceed fault
// when FAULT = 11: Digital mode, but power supply voltage too high (= poor efficiency)
// when FAULT = 12: Digital mode, but PA power output too high
// when FAULT = 13: Band Codes are Out of Range
// when FAULT = 14: Input Power is HIGH
// when FAULT = 15: LPF doesn't correspond to Band
void fault() {
if (FAULT == 0) return; // No fault !
// take vital actions !
digitalWrite(PTT_block,HIGH); // signal to sequencer that we force RX mode !
digitalWrite(supply_50v, LOW); // remove + 50v supply from pallet
// update TX / RX STATUS while in FAULT void
if (Operate == 1){
lcd.setCursor( 36,3 );
lcd.print( "-RX-");
}
else {
lcd.setCursor( 36,3 );
lcd.print( " ");
}
digitalWrite(Buzzer,HIGH); // Sound buzzer
// Signal on display
lcd.setCursor( 0,0 );
lcd.print (" F A U L T ! ");
lcd.setCursor( 0,1 );
if (FAULT == 1){
lcd.print(" OVERCURRENT 48-55v SUPPLY ");
// Show Error value
lcd.setCursor( 9,3 ); // Amps
if (amp_50 > 999) amp_50 = 999;
if (amp_50<100) lcd.print(" ");
lcd.print((amp_50/10), DEC);
lcd.print(".");
lcd.print((amp_50)%10, DEC);
}
if (FAULT == 2) {
lcd.print(" UNDER/OVERVOLTAGE 48-55v SUPPLY ");
// Show Volt_50 error value
lcd.setCursor( 9,2 );
if (volt_50 > 999) volt_50 = 999;
if (volt_50<100) lcd.print(" ");
lcd.print((volt_50/10), DEC);lcd.print(".");
lcd.print((volt_50)%10, DEC);
};
if (FAULT == 5) lcd.print(" POWER AMPLIFIER OVERDRIVE ");
if (FAULT == 6) lcd.print(" LOAD MISMATCH (SWR EXCEED) ");
if (FAULT == 7) lcd.print(" NTC TEMP. PROBE CIRCUIT ERROR ");
if (FAULT == 8) lcd.print(" UNDER/OVER TEMPERATURE ");
if((FAULT == 8) and (temp > 40 )) analogWrite (FANcontrol,255); // cooling down ...
if (FAULT == 9) lcd.print(" 50v SUPPLY IDLE CURRENT EXCEED ");
if (FAULT ==10) lcd.print(" SWR ERROR - POWER CYCLE SSPA ");
if (FAULT ==11) lcd.print("LIMIT SUPPLY VOLTAGE TO 48V IN DIGI MODE");
if (FAULT ==12) lcd.print(" LIMIT PA OUTPUT TO 1kW IN DIGI MODE ");
if (FAULT ==13) lcd.print(" The Band Data is Out Of Range ");
if (FAULT ==14) lcd.print(" HIGH INPUT POWER (Potential Overdrive!) ");
if (FAULT ==15) lcd.print(" LPF doesn't correspond to Band ");
delay (500);
digitalWrite(Buzzer,LOW);
lcd.setCursor( 0,0 );
lcd.print (" ");
delay (500);
if (digitalRead(IN_reset) == LOW) ResetFault();
fault();
}
/////////////////// END FAULT CONDITION ////////////////////
////////////////// RECOVER PROCEDURE /////////////////////
void ResetFault() {
digitalWrite(Buzzer,LOW);
lcd.setCursor( 0,1 );
lcd.print (" ");
lcd.setCursor( 0,0 );
if (PTT ==0) lcd.print (" R E S E T T I N G ");
if (PTT ==1) lcd.print (" R E S E T T I N G B U T ");
if (PTT ==0)delay (800);
lcd.setCursor( 0,0 );
if (PTT ==0)lcd.print (" R E S E T T I N G . ");
delay (800);
lcd.setCursor( 0,0 );
if (PTT ==0)lcd.print (" R E S E T T I N G . . ");
if (PTT ==0)delay (800);
lcd.setCursor( 0,0 );
if (PTT ==0)lcd.print (" R E S E T T I N G . . . ");
if (PTT ==0)delay (1500);
// update PTT status : restart / recover only in RX mode
if (digitalRead(IN_PTT) == HIGH) { // We are still in TX mode !
PTT = 1;
digitalWrite(Buzzer,HIGH);
lcd.setCursor( 0,0 );
lcd.print (" SET TRANSCEIVER IN RX PLEASE ! ");
delay (800);
ResetFault(); // stay in loop till RX mode
}
// Recovery
digitalWrite(Buzzer,LOW);
digitalWrite(PTT_block,LOW); // signal to sequencer that we allow again PTT mode !
FAULT = 0; //recover fault condition
printtemplate ();
SelfTest = 1; // Force Selftest
}
void updateLPFRelays() {
for (int i = 0; i < NUM_BANDS; i++) {
digitalWrite(LPFrelayPins[i], i == activeLPFRelay ? HIGH : LOW);
}
}
//////////////// RS232
bool readCAT() {
// Simulate CAT response
strcpy(responsePC, "PC030;");
strcpy(responseFA, "FA014074000;");
return true;
}
void printPower(const char* resp) {
if (strncmp(resp, "PC", 2) == 0) {
char numStr[4];
strncpy(numStr, resp + 2, 3);
numStr[3] = '\0';
INpower = atoi(numStr);
}
}
void printFrequencyAndBand(const char* resp) {
if (strncmp(resp, "FA", 2) == 0) {
char freqStr[10];
strncpy(freqStr, resp + 2, 9);
freqStr[9] = '\0';
long freqHz = atol(freqStr);
float freqMHz = freqHz / 1000000.0;
band = getBand(freqHz);
}
}
String getBand(long freqHz) {
if (freqHz >= 1800000 && freqHz < 2000000) return "160m";
if (freqHz >= 3500000 && freqHz < 4000000) return "80m";
if (freqHz >= 5330500 && freqHz <= 5403500) return "60m";
if (freqHz >= 7000000 && freqHz < 7300000) return "40m";
if (freqHz >= 10100000 && freqHz < 10150000) return "30m";
if (freqHz >= 14000000 && freqHz < 14350000) return "20m";
if (freqHz >= 18068000 && freqHz < 18168000) return "17m";
if (freqHz >= 21000000 && freqHz < 21450000) return "15m";
if (freqHz >= 24890000 && freqHz < 24990000) return "12m";
if (freqHz >= 28000000 && freqHz < 29700000) return "10m";
if (freqHz >= 50000000 && freqHz < 54000000) return "6m";
return "";
}
//////////////// RS23250V
Current
LPF-
LPF+
Oper<->Stby
RX<->TX
Reset
LPF_AUTO
LPF_MANUAL
160m
80m
40m
30m
20m
17m
15m
12m
10m
6m