ATS with NANO Board

Hello,
I'm working on an ATS with two sources: Main and Gen, using a Nano board. I have created the code as shown below, but I'm facing an issue with the priority between Main and Gen. Specifically, when Main goes offline, the ATS should switch to Gen after a delay of 5 seconds. However, when Main is offline and Gen is running, the ATS switches back to Main after a delay. I have tried to identify the issue in the code but couldn't find the problem. I would like to update the code to meet this condition. |

Condition Action
Main HIGH, Gen High

Main HIGH, Gen OFF|Wait MainDelay → switch to Main

Wait MainDelay → switch to Main|
|Main LOW, Gen HIGH|Wait GenDelay → switch to Generator, no switch to main if its LOW|
|Gen ON, Main returns|Immediately OFF Gen → Wait MainDelay → switch to Main|

I’m writing to seek your support and advice. and thanks to anyone help me.

Thanks
Norman

code :

#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

// Pin Definitions
const int mainPin = 2;
const int genPin = 3;
const int mainRelay = 4;
const int genRelay = 5;
const int setButton = 6;
const int upButton = 7;
const int downButton = 8;
const int buzzer = 9;

// EEPROM addresses
const int addrMainDelay = 0;
const int addrGenDelay = 1;
const int addrBuzzerEnable = 2;
const int addrMainTone = 3;
const int addrGenTone = 5;
const int addrToneDuration = 7;

// Settings Variables
int mainDelay = 5;
int genDelay = 5;
bool buzzerEnabled = true;
int mainTone = 1000;
int genTone = 2000;
int toneDuration = 200;

// UI and Timing
int menuIndex = 0;
bool inSettings = false;
bool saveSettings = false;
unsigned long buttonPressTime = 0;
bool longPressDetected = false;
unsigned long previousMillis = 0;
const long interval = 500;

// Icons
byte iconPower[8] = {B00100, B01110, B11111, B10101, B11111, B01110, B00100, B00000};
byte iconGear[8] = {B00000, B10101, B01110, B11111, B01110, B10101, B00000, B00000};

// Setup
void setup() {
pinMode(mainPin, INPUT);
pinMode(genPin, INPUT);
pinMode(mainRelay, OUTPUT);
pinMode(genRelay, OUTPUT);
pinMode(setButton, INPUT_PULLUP);
pinMode(upButton, INPUT_PULLUP);
pinMode(downButton, INPUT_PULLUP);
pinMode(buzzer, OUTPUT);

lcd.init();
lcd.backlight();
lcd.createChar(1, iconPower);
lcd.createChar(2, iconGear);

loadSettings();

tone(buzzer, 1500, 150); delay(200);
tone(buzzer, 2000, 150); delay(200);
tone(buzzer, 2500, 150); delay(500);

lcd.setCursor(0, 0);
lcd.print(" ATS Controller ");
lcd.setCursor(0, 1);
lcd.print("Initializing...");
delay(2000);
}

// Main Loop
void loop() {
unsigned long currentMillis = millis();

// Button Long Press to Enter/Exit Settings
if (digitalRead(setButton) == LOW && !longPressDetected) {
if (buttonPressTime == 0) buttonPressTime = currentMillis;
if (currentMillis - buttonPressTime > 1000) {
inSettings = !inSettings;
menuIndex = 0;
longPressDetected = true;
}
} else if (digitalRead(setButton) == HIGH) {
buttonPressTime = 0;
longPressDetected = false;
}

if (inSettings) {
handleSettings();
} else {
handlePowerLogic();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
showStatus();
}
}
}

// Handle Power Logic
void handlePowerLogic() {
bool m = digitalRead(mainPin) == HIGH;
bool g = digitalRead(genPin) == HIGH;
bool mainActive = digitalRead(mainRelay);
bool genActive = digitalRead(genRelay);

if (m && g) {
if (genActive) {
digitalWrite(genRelay, LOW); // Turn off Gen
showCountdownBar("Main", mainDelay);
digitalWrite(mainRelay, HIGH);
beep(mainTone);
} else if (!mainActive) {
showCountdownBar("Main", mainDelay);
digitalWrite(mainRelay, HIGH);
beep(mainTone);
}
} else if (m && !g) {
if (!mainActive) {
showCountdownBar("Main", mainDelay);
digitalWrite(mainRelay, HIGH);
digitalWrite(genRelay, LOW);
beep(mainTone);
}
} else if (!m && g) {
if (!genActive) {
digitalWrite(mainRelay, LOW);
showCountdownBar("Gen", genDelay);
digitalWrite(genRelay, HIGH);
beep(genTone);
}
} else {
// No power available
digitalWrite(mainRelay, LOW);
digitalWrite(genRelay, LOW);
}
}

// Countdown Bar
void showCountdownBar(String label, int seconds) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Wait: " + label);
for (int i = 0; i <= seconds; i++) {
lcd.setCursor(0, 1);
int barLen = map(i, 0, seconds, 0, 16);
for (int j = 0; j < 16; j++) {
lcd.print(j < barLen ? char(255) : ' ');
}
delay(1000);
}
}

// Show Status
void showStatus() {
bool m = digitalRead(mainPin) == HIGH;
bool g = digitalRead(genPin) == HIGH;
bool mainActive = digitalRead(mainRelay);
bool genActive = digitalRead(genRelay);

lcd.setCursor(0, 0);
lcd.print("M:"); lcd.print(m ? "ON " : "OFF");
lcd.print(" G:"); lcd.print(g ? "ON" : "OFF");

lcd.setCursor(0, 1);
if (mainActive) {
lcd.write(1); lcd.print(" Main Active ");
} else if (genActive) {
lcd.write(2); lcd.print(" Gen Active ");
} else {
lcd.print("No Power ");
}
}

// Settings Menu
void handleSettings() {
lcd.clear();
switch (menuIndex) {
case 0: lcd.print("Main Delay: "); lcd.print(mainDelay); lcd.print("s"); break;
case 1: lcd.print("Gen Delay: "); lcd.print(genDelay); lcd.print("s"); break;
case 2: lcd.print("Buzzer: "); lcd.print(buzzerEnabled ? "ON " : "OFF"); break;
case 3: lcd.print("Main Tone: "); lcd.print(mainTone); break;
case 4: lcd.print("Gen Tone: "); lcd.print(genTone); break;
case 5: lcd.print("Buzz Dur: "); lcd.print(toneDuration); lcd.print("ms"); break;
case 6: lcd.print("Save & Exit"); break;
}
delay(200);

if (digitalRead(upButton) == LOW) {
switch (menuIndex) {
case 0: if (mainDelay < 60) mainDelay++; break;
case 1: if (genDelay < 60) genDelay++; break;
case 2: buzzerEnabled = !buzzerEnabled; break;
case 3: mainTone += 100; break;
case 4: genTone += 100; break;
case 5: if (toneDuration < 1000) toneDuration += 50; break;
}
} else if (digitalRead(downButton) == LOW) {
switch (menuIndex) {
case 0: if (mainDelay > 1) mainDelay--; break;
case 1: if (genDelay > 1) genDelay--; break;
case 2: buzzerEnabled = !buzzerEnabled; break;
case 3: if (mainTone > 100) mainTone -= 100; break;
case 4: if (genTone > 100) genTone -= 100; break;
case 5: if (toneDuration > 50) toneDuration -= 50; break;
}
} else if (digitalRead(setButton) == LOW && !longPressDetected) {
menuIndex++;
if (menuIndex > 6) menuIndex = 0;
if (menuIndex == 6) saveSettings = true;
}

if (saveSettings) {
saveSettings = false;
saveToEEPROM();
inSettings = false;
}
}

// Beep
void beep(int frequency) {
if (buzzerEnabled) {
tone(buzzer, frequency, toneDuration);
}
}

// EEPROM Load
void loadSettings() {
mainDelay = constrain(EEPROM.read(addrMainDelay), 1, 60);
genDelay = constrain(EEPROM.read(addrGenDelay), 1, 60);
buzzerEnabled = EEPROM.read(addrBuzzerEnable);
EEPROM.get(addrMainTone, mainTone);
EEPROM.get(addrGenTone, genTone);
EEPROM.get(addrToneDuration, toneDuration);
}

// EEPROM Save
void saveToEEPROM() {
EEPROM.write(addrMainDelay, mainDelay);
EEPROM.write(addrGenDelay, genDelay);
EEPROM.write(addrBuzzerEnable, buzzerEnabled);
EEPROM.put(addrMainTone, mainTone);
EEPROM.put(addrGenTone, genTone);
EEPROM.put(addrToneDuration, toneDuration);
}

The first thing you need to do is read the pinned post re 'How to get the most from the forum'. Then re-post your code after doing an Auto Format under the Tools menu. Post the code in code tags either from the Edit menu or using the ^^^^^^^^ above.
Also define ATS

No clue what we’re discussing here ATS can refer to a lot of different things, from a vehicle to a small module, not to mention various software platforms. Since it looks like you're switching something based on sensor input, we really need an annotated schematic showing exactly how everything is wired. Please include all power sources, ground connections, and clearly note any wires longer than 10 inches (25 cm), as wire length can affect performance and signal integrity.

Hello gilshultz, Thank you for your reply and feedback. The ATS (Automatic Transfer Switch) is used to transfer power between two or more sources, such as the main supply and a generator. Attached is a simple schematic for the circuit I designed.

And code, in code tags, after auto format? The reason is, it is then dead easy to put your code into our IDE, or into a simulator. Until then, have fun.

Your project sounds plausible but there is a lot that needs to be considered that I do not see.

How will this be packaged, in a grounded metal enclosure?

It appears this will be mains voltage, what happens if both relays energize at the same time. You need to interlock them so this does not happen. Processors can mess up when getting transients.

If you reverse bias a 7805 voltage regulator, it can be damaged because the output voltage may exceed the input voltage, causing current to flow in the wrong direction. This is possible as the two outputs are connected and if an input fails? To prevent this, a diode is often placed across the regulator to safely redirect any reverse current.

You will probably need more then 5 seconds for the generator to stabilize. I suggest about 30 seconds. Then before going back to the mains be sure they are stable for at least 30 seconds.

You are missing bulk capacitors on the 5V and and high frequency bypass capacitors on both in and out of the 7805. If left as is it will probably sound like a buzzer. I would spend the extra penny and add another resistor so each LED has one as it is possible they both could be on.

What are you using for a watch dog?