// pulse an output if switch changes to low
// using a finite state machine
// https://forum.arduino.cc/t/switching-circuit-using-relays-and-ups-with-arduino/1019626/
#define DEBUG 1 // 1 debug messages, 0 off
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
int UPSrestrtCount = 0; // Integer for UPS restart count if it failed to start
int period = 2000; // Voltage update delay time (ms)
unsigned long time_now = 0;
const word PWM_FREQ_HZ = 25000; //Adjust this value to adjust the frequency
const word TCNT1_TOP = 16000000/(2*PWM_FREQ_HZ);
#define SIGNAL_PIN A0 // Define analog input for voltage measurement
float adc_voltage = 0.0; // Floats for ADC voltage
float in_voltage = 0.0; // Floats for Input voltage
float R1 = 100000.0; // Resistor values in Voltage divider (in ohms)
float R2 = 20000.0; // Resistor values in Voltage divider (in ohms)
float ref_voltage = 5.0; // Float for Reference Voltage
float thres_voltage = 1.007222216606498; // Threshold voltage for calibration
float CHOFF_voltage = 28.1; // Charger Turn OFF voltage
float CHON_voltage = 27.3; // Charger Turn ON voltage
int adc_value = 0; // Integer for ADC value
byte Battery[] = {
0x00,
0x04,
0x0E,
0x0E,
0x0E,
0x0E,
0x0E,
0x00
};
byte BatteryCharging[] = {
0x00,
0x02,
0x04,
0x08,
0x1E,
0x04,
0x08,
0x10
};
/* The easy circuit for temperature measurement with 10K thermistor:
* Analog pin 2,3
* |
* 5V |-----/\/\/\-----+-----/\/\/\-----| GND
* ^ ^
* 10K thermistor 10K resistor
*/
#define ThermistorPin A2 // First 10K thermistor connected at PIN A2 (For Ambient Temp. Measurement)
#define Thermistor2Pin A3 // Second 10K thermistor connected at PIN A3 (For UPS Temp. Measurement )
long A2_Value;
long A3_Value;
float RT1 = 10000; // Value of Resistor connected in series with First Thermistor
float logRT2, RT2, T;
float RT3 = 10000; // Value of Resistor connected in series with Second Thermistor
float logRT4, RT4, T2;
float c1 = 0.001129148, c2 = 0.000234125, c3 = 0.0000000876741; //steinhart-hart coeficients for thermistor
float temp_c, temp_c2, temp_f, temp_f2;
float FNOFF_temp = 1; // Minimum Temperature difference in °C at which FANs are Turned OFF
float FNON_temp = 4; // Minimum Temperature difference in °C at which FANs are Turned ON
byte FAN1[] = {
0x00,
0x00,
0x04,
0x04,
0x1F,
0x04,
0x04,
0x00
};
byte FAN2[] = {
0x00,
0x00,
0x11,
0x0A,
0x04,
0x0A,
0x11,
0x00
};
#if defined(ARDUINO) && ARDUINO >= 100
#define printByte(args) write(args);
#else
#define printByte(args) print(args,BYTE);
#endif
LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address to 0x27 for a 16 chars and 2 line display
constexpr uint8_t UPSsupply {4}; // Current state of UPS (ON/OFF)
constexpr uint8_t FANrelay {6}; // Cooling FAN relay
constexpr uint8_t MAINSsupply {7}; // AC power detection using a 5v charger
constexpr uint8_t button {8}; // Push button detection
const byte OC1A_PIN = 9;
constexpr uint8_t CHARGErelay {10}; // Battery Charger relay
//constexpr uint8_t upsSwitch {11}; // connected to UPS on/off switch
constexpr uint8_t MAINSrelay {12}; // MAINS relay
constexpr uint8_t UPSrelay {13}; // UPS relay
constexpr uint16_t intervalOn {0}; // pulse time for UPS ON signal
constexpr uint16_t intervalOff {0}; // pulse time for UPS OFF signal
constexpr uint16_t intervalWarning {0}; // time before UPS will get switched on
constexpr uint16_t intervalUPS {0}; // Waiting Time for UPS to turn off properly.
constexpr uint16_t intervalRecover {0}; // time before UPS will be switched off
constexpr uint16_t intervalCheckMAINS {0}; // MAINS checking Time
constexpr uint16_t intervalLCD {0}; // LCD light TURN ON Time
enum class State {INITIALIZE, // Check if Mains is available
IDLE, // AC is on, waiting fo AC black out
WARNING, // AC is off, waiting for UPS switch on
MAINSRELAY_OFF,
UPSRELAY_ON,
FAIL, // AC is off, waiting for AC coming back
RECOVER, // AC is on, recovering period, AC is back but we keep UPS running
UPSRELAY_OFF, // UPS RELAY Off
BACK // preparing things back for idle
};
// a class to pulse a pin - nonblocking
class PulsePin
{
const uint8_t pin;
uint32_t previousMillis = 0;
uint32_t lcdPreviousMillis = 0;
bool state = LOW; // state of the pin
uint16_t interval = intervalOn;
public:
PulsePin (const uint8_t pin ) : pin (pin) {}
void begin()
{
pinMode(pin, OUTPUT);
}
void pulse(uint16_t interval)
{
this->interval = interval;
digitalWrite(pin, HIGH);
previousMillis = millis();
state = HIGH;
}
void update()
{
if (state && millis() - previousMillis > interval )
{
digitalWrite(pin, LOW);
state = LOW;
}
}
};
PulsePin upsSwitch(11);
void checkAC()
{
static uint32_t previousMillis = 0; // timestamp for state machine
static uint32_t lcdPreviousMillis = 0;
static State state {State::INITIALIZE}; // current state
static uint8_t previousInputState = HIGH; // previous state of input circuit / AC
uint8_t inputState = digitalRead(MAINSsupply); // current state of input circuit / AC
uint8_t UPSstate = digitalRead(UPSsupply); // current state of UPS (ON/OFF)
static uint8_t previousinputbutton = HIGH; // previous state of push button
uint8_t inputbutton = digitalRead(button); // current state of push button
static uint8_t CHARGER = HIGH;
static uint8_t FAN = LOW;
static uint8_t UPS = LOW;
switch (state)
{ case State::INITIALIZE :
lcd.setCursor(0, 0 );
lcd.print("INITIALIZING ");
lcd.setCursor(0, 1 );
lcd.print("PLEASE WAIT...");
UPSrestrtCount = 0;
digitalWrite(CHARGErelay, HIGH); // Battery Charger turned ON
digitalWrite(FANrelay, HIGH); // FANs turned ON
setPwmDuty(100); // Full Speed
FAN = HIGH ;
if (millis() - previousMillis > intervalUPS)
{
if (inputState == HIGH && UPSstate == HIGH ) // check for AC presence
if (millis() - previousMillis > intervalRecover)
{
Serial.println(F("\nAC ON, UPS ON - Turn OFF UPS - Re-Initialize"));
lcd.setCursor(0, 0 );
lcd.print("UPS OFF ");
digitalWrite(UPSrelay, LOW); // UPS relay turned OFF
digitalWrite(MAINSrelay, LOW); // MAINS relay turned OFF(MAINS ON)
upsSwitch.pulse(intervalOff); // switching UPS OFF
previousMillis = millis();
lcd.clear();
lcd.setCursor(14, 1);
lcd.print ("ON");
state = State::INITIALIZE; // Go back to Intialize Mode
}
if (inputState == HIGH && UPSstate == LOW ) // check for AC presence
if (millis() - previousMillis > intervalWarning)
{
Serial.println(F("\nAC ON - Enter IDLE"));
digitalWrite(UPSrelay, LOW); // UPS relay turned OFF
digitalWrite(MAINSrelay, LOW); // MAINS relay turned OFF(MAINS ON)
previousMillis = millis();
//lcd.clear();
//lcd.setCursor(14, 1);
//lcd.print ("ON");
setPwmDuty(10);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("IDLE... ");
lcd.setCursor(9, 0);
lcd.write(byte(0));
lcd.createChar(0,BatteryCharging);
state = State::IDLE;
}
if (inputState == LOW && UPSstate == LOW ) // check for AC fail
if (millis() - previousMillis > intervalWarning)
{
Serial.println(F("\nAC off - Enter WARNING"));
previousMillis = millis();
lcd.clear();
state = State::WARNING;
}
if (inputState == LOW && UPSstate == HIGH ) // check for AC fail
if (millis() - previousMillis > intervalRecover)
{
Serial.println(F("\nAC off, UPS ON - Turn OFF UPS - Re-Initialize"));
previousMillis = millis();
digitalWrite(UPSrelay, LOW); // UPS relay turned OFF
digitalWrite(MAINSrelay, LOW); // MAINS relay turned OFF(MAINS ON)
upsSwitch.pulse(intervalOff); // switching UPS OFF
lcd.clear();
state = State::INITIALIZE; // Go back to Intialize Mode
}
}
break;
case State::IDLE :
//lcd.setCursor(0, 0);
//lcd.print("IDLE... ");
for(int i=0; i< 49; i++) {
adc_value = analogRead(SIGNAL_PIN); // measure voltage at PIN A0
}
in_voltage = adc_value * (5.0/1024)*((R1 + R2)/R2);
in_voltage = thres_voltage * in_voltage; // Threshold For Voltage calibration.
//adc_voltage = (adc_value * ref_voltage) / 1024.0;
//in_voltage = adc_voltage / (R2 / (R1 + R2)) ;
//digitalWrite(CHARGErelay, HIGH); // CHARGER turned ON
if (in_voltage >= CHOFF_voltage && CHARGER == HIGH) // if battery voltage is >= 28.1 , turn OFF charger
{
digitalWrite(CHARGErelay, LOW); // Battery Charger turned OFF
//lcd.setCursor(13, 1);
//lcd.print (" ");
lcd.setCursor(9, 0);
lcd.write(byte(0));
//lcd.print ("OFF");
lcd.createChar(0,Battery);
Serial.println(F("\nCharger turned OFF"));
digitalWrite(FANrelay, LOW); // FANs turned OFF
FAN = LOW ;
CHARGER = LOW;
}
if (in_voltage <= CHON_voltage && CHARGER == LOW) // if battery voltage is <= 27.5 , turn ON charger
{
digitalWrite(CHARGErelay, HIGH); // Battery Charger turned ON
lcd.setCursor(9, 0);
lcd.write(byte(0));
//lcd.print (" ON");
lcd.createChar(0,BatteryCharging);
Serial.println(F("\nCharger turned ON"));
digitalWrite(FANrelay, HIGH); // FANs turned ON
FAN = HIGH;
CHARGER = HIGH;
}
if(millis() >= time_now + period){
time_now += period;
//Serial.print("Input Voltage = ");
//Serial.println(in_voltage, 2);
lcd. setCursor (10, 0);
lcd.print (in_voltage, 2);
lcd.print ("V");
//Serial.println(" ");
A2_Value=0; // Start Temperature calculation for First thermistor (For Ambient) on PIN A2
for(int i=0; i< 50; i++) {
A2_Value = A2_Value + analogRead(ThermistorPin);
}
A2_Value=A2_Value/50;
RT2 = RT1 * (1023.0 / (float)A2_Value - 1.0); //calculate resistance on thermistor
logRT2 = log(RT2);
temp_c = (1.0 / (c1 + c2*logRT2 + c3*logRT2*logRT2*logRT2)); // temperature in Kelvin
temp_c = temp_c - 273.15; //convert Kelvin to Celcius
temp_f = (temp_c * 9.0)/ 5.0 + 32.0; //convert Celcius to Fahrenheit
lcd.setCursor (0, 1 );
lcd.print(temp_c, 1);
lcd.write(0xdf); // to display °
lcd.print("C ");
A3_Value=0; // Start Temperature calculation for Second thermistor (For UPS) on PIN A3
for(int i=0; i< 50; i++) {
A3_Value = A3_Value + analogRead(Thermistor2Pin);
}
A3_Value=A3_Value/50;
RT4 = RT3 * (1023.0 / (float)A3_Value - 1.0); //calculate resistance on thermistor
logRT4 = log(RT4);
temp_c2 = (1.0 / (c1 + c2*logRT4 + c3*logRT4*logRT4*logRT4)); // temperature in Kelvin
temp_c2 = temp_c2 - 273.15; //convert Kelvin to Celcius
temp_f2 = (temp_c2 * 9.0)/ 5.0 + 32.0; //convert Celcius to Fahrenheit
lcd.setCursor (10, 1 );
lcd.print(temp_c2, 1);
lcd.write(0xdf); // to display °
lcd.print("C ");
if(temp_c2 - temp_c >= FNON_temp && FAN == LOW){ // if Temp. difference of both thermistor is more than FANON_temp, turn ON FAN
digitalWrite(FANrelay, HIGH); // FAN turned ON
setPwmDuty(10);
Serial.print("FAN ON");
FAN = HIGH;
}
if(temp_c2 - temp_c <= FNOFF_temp && FAN == HIGH){ // if Temp. difference of both thermistor is more than FANOFF_temp, turn OFF FAN
digitalWrite(FANrelay, LOW);
setPwmDuty(0);
Serial.print("FAN OFF");
FAN = LOW;
}
}
if (millis() - lcdPreviousMillis>=intervalLCD)
{
lcd.noBacklight();
}
if (digitalRead(button) == HIGH)
{
Serial.println(F("\nButton Pressed"));
previousMillis = millis();
lcdPreviousMillis = millis();
lcd.backlight();
lcd.setCursor (10, 1 );
lcd.print(temp_c, 1);
}
if (millis() - lcdPreviousMillis>=intervalLCD)
{
lcd.noBacklight();
}
if (inputState == LOW && previousInputState == HIGH) // check for AC fail
{
Serial.println(F("\nAC off - Enter WARNING"));
previousMillis = millis();
lcdPreviousMillis = millis();
lcd.backlight();
state = State::WARNING;
}
break;
case State::WARNING :
lcd.setCursor(0, 0 );
lcd.print("WARNING ");
lcd.setCursor(0, 1 );
lcd.print("POWER FAIL ");
if (inputState == HIGH && previousInputState == LOW) // check for AC back
{
Serial.println(F("\nAC on again - go back to IDLE"));
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("IDLE... ");
lcd.setCursor(9, 0);
lcd.write(byte(0));
lcd.createChar(0,BatteryCharging);
state = State::IDLE;
}
if (millis() - previousMillis > intervalWarning)
{
Serial.println(F("\nAC off --> MAINSRELAY_OFF"));
digitalWrite(MAINSrelay, HIGH); // MAINS relay turned ON (MAINS OFF)
previousMillis = millis();
state = State::MAINSRELAY_OFF;
}
break;
case State::MAINSRELAY_OFF :
lcd.setCursor(0, 0 );
lcd.print("TURNING ");
lcd.setCursor(0, 1 );
lcd.print("MAINS RELAY OFF ");
if (inputState == HIGH && previousInputState == LOW) // check for AC back again
{
Serial.println(F("\nAC on again Turning MAINSrealy ON and go back to IDLE"));
previousMillis = millis();
digitalWrite(MAINSrelay, LOW); // MAINS relay turned OFF again (MAINS ON)
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("IDLE... ");
lcd.setCursor(9, 0);
lcd.write(byte(0));
lcd.createChar(0,BatteryCharging);
state = State::IDLE;
}
if (millis() - previousMillis > intervalWarning)
{
Serial.println(F("\nAC off --> UPSRELAY_ON"));
digitalWrite(UPSrelay, HIGH); // UPS relay turned ON
previousMillis = millis();
state = State::UPSRELAY_ON;
}
break;
case State::UPSRELAY_ON :
lcd.setCursor(0, 0 );
lcd.print("TURNING ");
lcd.setCursor(0, 1 );
lcd.print(" ON UPS ");
if (inputState == HIGH && previousInputState == LOW) // check for AC back again
{
Serial.println(F("\nAC on again Turning MAINSrealy ON and go back to IDLE"));
previousMillis = millis();
digitalWrite(UPSrelay, LOW); // UPS relay turned OFF again
digitalWrite(MAINSrelay, LOW); // MAINS relay turned OFF again (MAINS ON)
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("IDLE... ");
lcd.setCursor(9, 0);
lcd.write(byte(0));
lcd.createChar(0,BatteryCharging);
state = State::IDLE;
}
if (millis() - previousMillis > intervalWarning)
{
Serial.println(F("\nAC off --> FAIL"));
lcdPreviousMillis = millis();
previousMillis = millis();
upsSwitch.pulse(intervalOn); // switching UPS ON
digitalWrite(CHARGErelay, LOW); // Battery Charger turned OFF
digitalWrite(FANrelay, HIGH); // FANs turned ON
setPwmDuty(100);
FAN = HIGH ;
state = State::FAIL;
}
break;
case State::FAIL :
lcd.setCursor(0, 0 );
lcd.print("MAINS FAIL");
lcd.setCursor(0, 1 );
//lcd.print(" LOAD ON UPS ");
adc_value = analogRead(SIGNAL_PIN);
in_voltage = adc_value * (5.0/1024)*((R1 + R2)/R2);
in_voltage = thres_voltage * in_voltage; // Threshold For Voltage calibration.
//adc_voltage = (adc_value * ref_voltage) / 1024.0;
//in_voltage = adc_voltage / (R2 / (R1 + R2)) ;
if(millis() >= time_now + period){
time_now += period;
Serial.print("Input Voltage = ");
Serial.println(in_voltage, 2);
lcd. setCursor (10, 0);
lcd.print (in_voltage, 2);
lcd.print ("V ");
Serial.println(" ");
A2_Value=0; // Start Temperature calculation for First thermistor (For Ambient) on PIN A2
for(int i=0; i< 50; i++) {
A2_Value = A2_Value + analogRead(ThermistorPin);
}
A2_Value=A2_Value/50;
RT2 = RT1 * (1023.0 / (float)A2_Value - 1.0); //calculate resistance on thermistor
logRT2 = log(RT2);
temp_c = (1.0 / (c1 + c2*logRT2 + c3*logRT2*logRT2*logRT2)); // temperature in Kelvin
temp_c = temp_c - 273.15; //convert Kelvin to Celcius
temp_f = (temp_c * 9.0)/ 5.0 + 32.0; //convert Celcius to Fahrenheit
lcd.setCursor (0, 1 );
lcd.print(temp_c, 1);
lcd.write(0xdf); // to display °
lcd.print("C ");
A3_Value=0; // Start Temperature calculation for Second thermistor (For UPS) on PIN A3
for(int i=0; i< 50; i++) {
A3_Value = A3_Value + analogRead(Thermistor2Pin);
}
A3_Value=A3_Value/50;
RT4 = RT3 * (1023.0 / (float)A3_Value - 1.0); //calculate resistance on thermistor
logRT4 = log(RT4);
temp_c2 = (1.0 / (c1 + c2*logRT4 + c3*logRT4*logRT4*logRT4)); // temperature in Kelvin
temp_c2 = temp_c2 - 273.15; //convert Kelvin to Celcius
temp_f2 = (temp_c2 * 9.0)/ 5.0 + 32.0; //convert Celcius to Fahrenheit
lcd.setCursor (10, 1 );
lcd.print(temp_c2, 1);
lcd.write(0xdf); // to display °
lcd.print("C ");
}
if (millis() - lcdPreviousMillis>=intervalLCD)
{
lcd.noBacklight();
}
if (digitalRead(button) == HIGH)
{
Serial.println(F("\nButton Pressed"));
previousMillis = millis();
lcdPreviousMillis = millis();
lcd.backlight();
}
if (millis() - lcdPreviousMillis>=intervalLCD)
{
lcd.noBacklight();
}
if (UPSstate == LOW && UPSrestrtCount<=2 ) // Try to restart UPS in case of Fault.
if (millis() - previousMillis > intervalCheckMAINS)
{
Serial.println(F("\nUPS fault Turning ON again"));
previousMillis = millis();
upsSwitch.pulse(intervalOff); // switching UPS ON
UPSrestrtCount = UPSrestrtCount + 1;
}
if (inputState == HIGH && previousInputState == LOW)
{
Serial.println(F("\nAC on --> Checking If Mains is Stable"));
previousMillis = millis();
lcd.backlight();
state = State::RECOVER;
}
break;
case State::RECOVER :
lcd.setCursor(0, 0 );
lcd.print("MAINS OK ");
lcd.setCursor(0, 1 );
lcd.print("SWITCHINGTOMAINS");
if (inputState == LOW && previousInputState == HIGH)
{
Serial.println(F("\nAC off - Not Stable --> go back to FAIL"));
lcd.backlight();
lcdPreviousMillis = millis();
state = State::FAIL;
}
if (millis() - previousMillis > intervalCheckMAINS)
{
Serial.println(F("\nAC on - MAINS Stable --> UPSRELAY_OFF"));
digitalWrite(UPSrelay, LOW); // UPS relay turned OFF
previousMillis = millis();
state = State::UPSRELAY_OFF;
}
break;
case State::UPSRELAY_OFF :
lcd.setCursor(0, 0 );
lcd.print("TURNING ");
lcd.setCursor(0, 1 );
lcd.print("UPS RELAY OFF ");
if (inputState == LOW && previousInputState == HIGH) // check for AC fail again in between
{
Serial.println(F("\nAC OFF in Between, Turning UPSRELAY ON again"));
digitalWrite(UPSrelay, HIGH); // UPS relay turned ON again
previousMillis = millis();
state = State::FAIL;
}
if (millis() - previousMillis > intervalRecover)
{
Serial.println(F("\nAC on --> Turn OFF UPS"));
upsSwitch.pulse(intervalOff); // switching UPS OFF
previousMillis = millis();
state = State::BACK;
}
break;
case State::BACK :
lcd.setCursor(0, 0 );
lcd.print("GOING BACK");
lcd.setCursor(0, 1 );
lcd.print(" TO IDLE MODE ");
if (inputState == LOW && previousInputState == HIGH) // check for AC fail again in between
{
Serial.println(F("\nAC OFF in Between, Turning UPSRELAY ON again"));
previousMillis = millis();
state = State::INITIALIZE;
}
if (millis() - previousMillis > intervalRecover)
{
Serial.println(F("\nAC on - longer than timelimit --> IDLE again"));
digitalWrite(MAINSrelay, LOW); // MAINS relay turned OFF (MAINS ON)
previousMillis = millis();
lcdPreviousMillis = millis();
lcd.clear();
CHARGER = HIGH;
setPwmDuty(10);
digitalWrite(CHARGErelay, HIGH); // Battery Charger turned OFF
state = State::INITIALIZE;
//state = State::IDLE;
}
break;
}
previousInputState = inputState;
delay(20); // this dirty delay is just to debounce switches
}
// this function gets called during delays.
// so we use it also to check if we have to do something with the pulsePin
void yield()
{
upsSwitch.update();
}
void setup() {
lcd.init(); // initialize the lcd
lcd.backlight();
Serial.begin(115200);
Serial.println(F("Initializing Please Wait..."));
lcd.createChar(0, Battery);
lcd.createChar(0, BatteryCharging);
upsSwitch.begin();
pinMode(UPSrelay, OUTPUT);
pinMode(MAINSrelay, OUTPUT);
pinMode(CHARGErelay, OUTPUT);
pinMode(FANrelay, OUTPUT);
pinMode(button, INPUT);
//pinMode(UPSsupply, INPUT);
pinMode(OC1A_PIN, OUTPUT);
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
// Set Timer1 configuration
// COM1A(1:0) = 0b10 (Output A clear rising/set falling)
// COM1B(1:0) = 0b00 (Output B normal operation)
// WGM(13:10) = 0b1010 (Phase correct PWM)
// ICNC1 = 0b0 (Input capture noise canceler disabled)
// ICES1 = 0b0 (Input capture edge select disabled)
// CS(12:10) = 0b001 (Input clock select = clock/1)
TCCR1A |= (1 << COM1A1) | (1 << WGM11);
TCCR1B |= (1 << WGM13) | (1 << CS10);
ICR1 = TCNT1_TOP;
delay(50);
}
void setPwmDuty(byte duty) {
OCR1A = (word) (duty*TCNT1_TOP)/100;
}
void loop() {
checkAC();
upsSwitch.update(); // must be called in loop - no delays allowed ;-)
}