/*
File : Outseal to MEGA ATS Only
Author : leveletech.com / wa 081214584114
Date : 2024 06 02
Description : Translate Ladder Outseal with ATS Application to MEGA
reference file ATS PLC REALISASI R2 .plc
*/
//--------------------------------- Include Libraries-----------------------------------------------
#include <LiquidCrystal_I2C.h>
//--------------------------------- Include Libraries-----------------------------------------------
//--------------------------------- Hardware Setting------------------------------------------------
int value0, value1, value2, value3, value6;
//--------------------------------- Hardware Setting------------------------------------------------
//--------------------------------- Define and Constant---------------------------------------------
// Assign push button pins
const int numButtons = 15; // Number of buttons
const int buttonPins[numButtons] = {7, 6, 5, 4, 3, 2, 42, 43, 44, 45, 46, 47, 48, 49, 50}; // Array of pins for buttons
bool lastButtonState[numButtons] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false};
// Assign relay pins
const int numRelays = 20; // Number of relays
const int relayPins[numRelays] = {22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41}; // Array of pins for relays
const int lcdAddress1 = 0x27;
//--------------------------------- Define and Constant---------------------------------------------
//--------------------------------- User Define Data Type-------------------------------------------
struct timer_data {
bool IN;
bool Q;
bool TT;
bool BP1; //button pressed
bool BP2; //button pressed
int PT; //preset time
int ET; //elapsed time
unsigned long TN;//time now
};
struct counter_data {
bool IN;
bool R; //Reset
bool Q;
bool BP; //button pressed
int PV; //preset value
int CV; //current value
};
struct spwm_data {
bool IN;
bool Q;
bool BP; //button pressed
int PT_on; //preset time for ON state
int PT_off; //preset time for OFF state
int ET; //elapsed time
unsigned long TN;//time now
};
struct osr_data {
bool IN;
bool Q;
bool BP;
};
//--------------------------------- User Define Data Type-------------------------------------------
//----------------------------------Global Variable-------------------------------------------------
bool S[numButtons + 1] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false};
bool S_P[numButtons + 1] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false};
bool R[numRelays + 1] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false};
bool R_P[numRelays + 1] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false};
bool B[10] = {false, false, false, false, false, false, false, false, false, false};
bool BLYNKING[8] = {false, false, false, false, false, false, false, false}; //blynking flag
unsigned long lastDebounceTime[numButtons]; // Array to store the last debounce time for each button
const unsigned long debounceDelay = 50; // Debounce delay in milliseconds
timer_data T[9]; //TON
counter_data CTU_data[2];
spwm_data spwm_dat[4];
osr_data OSR_dat[10];
LiquidCrystal_I2C lcd1 (lcdAddress1, 20, 4);
//----------------------------------Global Variable-------------------------------------------------
//----------------------------------User Defined Function Stage 1-----------------------------------
void TON(timer_data & Var) { //Timer On delay function
if (Var.IN)
{
if (!Var.BP1)
{
Var.TN = millis();
Var.BP1 = true;
Var.TT = true;
}
if (!Var.Q)
{
Var.ET = (millis() - Var.TN) / 1000;
if (Var.ET >= Var.PT) {
Var.Q = true;
Var.TT = false;
}
}
} else
{
Var.Q = false;
Var.BP1 = false;
Var.ET = 0;
Var.TT = false;
}
}
void TOF(timer_data & Var) { //Timer Off delay function
if (Var.IN && !Var.BP1) {
Var.Q = true;
Var.BP1 = true;
Var.BP2 = false;
}
if (!Var.IN && Var.BP1 && !Var.BP2) {
Var.TN = millis();
Var.BP1 = false;
Var.BP2 = true;
}
if (Var.Q == true && Var.BP2 == true) Var.ET = (millis() - Var.TN) / 1000;
if (millis() - Var.TN > Var.PT && Var.BP2)
{
Var.Q = false;
Var.BP2 = false;
Var.ET = 0;
}
}
void CTU(counter_data & Var) { //Counter Up
if (Var.IN) {
if (!Var.BP && !Var.Q)
{
Var.CV++;
Var.BP = true;
if (Var.CV >= Var.PV) Var.Q = true;
}
} else {
Var.BP = false;
}
if (Var.R) {
Var.CV = 0;
Var.Q = false;
}
}
void spwm(spwm_data & Var) { // Software PWM or Timer ON OFF
if (Var.IN)
{
if (!Var.BP)
{
Var.TN = millis();
Var.BP = true;
Var.Q = true; // Initially turn ON the output
}
Var.ET = (millis() - Var.TN) / 1000;
if (Var.Q && Var.ET >= Var.PT_on) {
Var.Q = false; // If ON time is over, turn OFF the output
Var.TN = millis(); // Reset the timer for the next state
}
else if (!Var.Q && Var.ET >= Var.PT_off) {
Var.Q = true; // If OFF time is over, turn ON the output
Var.TN = millis(); // Reset the timer for the next state
}
}
else
{
Var.Q = false; // If input is inactive, turn OFF the output
Var.BP = false; // Reset button pressed flag
Var.ET = 0; // Reset elapsed time
}
}
void OSR(osr_data & Var) {
if (Var.IN) {
if (!Var.BP) {
Var.Q = true;
} else {
Var.Q = false;
}
} else {
Var.BP = false;
Var.Q = false;
}
}
void DI_Update() { //Update Digital Input Value
for (int i = 0; i < numButtons; i++) {
// Read the current state of the button
int currentState = !digitalRead(buttonPins[i]);
// Check if the button state has changed and debounce it
if (currentState != lastButtonState[i]) {
// Reset the debounce timer
lastDebounceTime[i] = millis();
}
// Check if enough time has passed to consider it a valid state change
if ((millis() - lastDebounceTime[i]) > debounceDelay) {
// If enough time has passed, update the state
S[i + 1] = !currentState;
}
// Store the current state for the next iteration
lastButtonState[i] = currentState;
}
}
void DO_Update() { //Update Digital Output Value
for (int i = 1; i < numRelays + 1; i++) {
digitalWrite(relayPins[i - 1], !R[i]);
}
}
String R_Status(bool status) {
if (status) {
return "ON ";
} else {
return "OFF";
}
}
void LCD1_Update(bool R[], bool R_P[], int size) {
bool same = true;
// Check if every member of R and R_P is the same
for (int i = 0; i < size; i++) {
if (R[i] != !R_P[i]) {
same = false;
break;
}
}
// If not same, print "ok" on serial and update R_P with R values
if (!same) {
lcd1.setCursor(3, 0);
lcd1.print(" ALI PLC");
lcd1.setCursor(0, 1);
lcd1.print("PLN :" + R_Status(R[1]) + " " + "GENSET:" + R_Status(R[6]) );
lcd1.setCursor(0, 2);
lcd1.print("CRANK:" + R_Status(R[3] || R[4] || R[5]) + " " + "STOP :" + R_Status(R[2]));
lcd1.setCursor(0, 3);
lcd1.print("LVMDP :" + R_Status(R[9]) + " " + "ALARM:" + R_Status(R[7]) );
// Update R_P with R values
for (int i = 0; i < size; i++) {
R_P[i] = R[i];
}
}
}
void blynking_LCD() {
if (S[1] && S[6]) {
T[8].IN = !T[8].Q; TON(T[8]);
if (T[8].Q) {
if (BLYNKING[1]) {
lcd1.setCursor(0, 1);
lcd1.print("PLN");
BLYNKING[1] = false;
} else {
lcd1.setCursor(0, 1);
lcd1.print(" ");
BLYNKING[1] = true;
}
}
} else {
lcd1.setCursor(0, 1);
lcd1.print("PLN");
BLYNKING[1] = false;
}
}
void ATS_System() { //ATS System
//Rung 0 : EMERGENZY
//==================================================================================================
R[20] = !S[4];
//==================================================================================================
//Rung 1 : PLN ON
//==================================================================================================
T[1].IN = (!R[20] && S[1]); TON(T[1]); R[1] = T[1].Q;
//==================================================================================================
//Rung 2 :
//==================================================================================================
T[4].IN = R[1]; TON(T[4]); R[2] = T[4].TT ;
//==================================================================================================
//Rung 3 : ACU 12 VOLT DC >5 V
//==================================================================================================
T[2].IN = (S[5] && !R[1] && T[4].Q); TON(T[2]); B[1] = T[2].TT;
//==================================================================================================
//Rung 4 : TRIGER DC CRENG 1
//==================================================================================================
T[3].IN = (S[5] && !R[1] && !B[1]); TON(T[3]); R[3] = T[3].TT;
//==================================================================================================
//Rung 5 : CRENGKING ON 1
//==================================================================================================
R[10] = (!R[20] && S[6] && T[3].Q && !R[1]);
//==================================================================================================
//Rung 6 : TRIGER DC CRENG 2
//==================================================================================================
T[5].IN = (!R[20] && !R[1] && !R[3] && S[5] && !R[10]); TON(T[5]); R[4] = T[5].TT;
//==================================================================================================
//Rung 7 : CRENGKING ON 2
//==================================================================================================
R[11] = (!R[20] && S[6] && T[5].IN && !R[1]);
//==================================================================================================
//Rung 8 : TR5GER DC CRENGKING 3
//==================================================================================================
T[7].IN = (S[5] && !R[1] && !R[4] && T[5].Q && !R[11]); TON(T[7]);
T[6].IN = (T[7].TT && T[7].IN) ; TON(T[6]); R[5] = (T[6].TT && T[6].IN);
//==================================================================================================
//Rung 9 : CRENGKING ON 3
//==================================================================================================
R[12] = (!R[20] && S[6] && S[5] && T[7].Q && !R[1]);
//==================================================================================================
//Rung 10 :
//==================================================================================================
R[7] = (!R[20] && !R[1] && T[6].Q && !R[5] && !R[6]);
//==================================================================================================
//Rung 11 : KONDISI GENZED
//==================================================================================================
R[6] = (((T[3].Q && R[10]) || (T[5].Q && !R[10] && R[11]) || (T[7].Q && !R[10] && !R[11] && R[12])) && !R[20]);
//==================================================================================================
//Rung 12 : SWITCENG ATS
//==================================================================================================
R[8] = (!R[20] && !R[6] && R[1]) || (!R[20] && !R[1] && R[6]);
//==================================================================================================
//Rung 13 : LVMDP 1
//==================================================================================================
OSR_dat[1].IN = (S[2] && R[8] && B[2]); OSR(OSR_dat[1]); B[2] = OSR_dat[1].Q;
//==================================================================================================
//Rung 14 : B2 LVMDP 1
//==================================================================================================
R[9] = (B[2] && !R[9]) || (R[9] && !B[2]);
//==================================================================================================
//Rung 15 : LVMDP 2
//==================================================================================================
OSR_dat[2].IN = (S[3] && R[8] && B[3]); OSR(OSR_dat[2]); B[3] = OSR_dat[2].Q;
//==================================================================================================
//Rung 16 :
//==================================================================================================
R[13] = (B[3] && !R[13]) || (!B[3] && R[13]);
//==================================================================================================
//Rung 17 : SDP 1/1
//==================================================================================================
OSR_dat[3].IN = (R[9] && B[4]); OSR(OSR_dat[3]); B[4] = OSR_dat[3].Q;
//==================================================================================================
//Rung 18 :
//==================================================================================================
R[14] = (B[4] && !R[14]) || ( R[14] && !B[4]);
//==================================================================================================
//Rung 19 : SDP 1/2
//==================================================================================================
OSR_dat[4].IN = (R[9] && B[5]); OSR(OSR_dat[4]); B[5] = OSR_dat[4].Q;
//==================================================================================================
//Rung 20 :
//==================================================================================================
R[15] = (R[9] && B[5] && !R[15]) || ( R[9] && !B[5] && R[15]);
//==================================================================================================
//Rung 21 : SDP 3/1
//==================================================================================================
OSR_dat[5].IN = (R[9] && B[6]); OSR(OSR_dat[5]); B[6] = OSR_dat[5].Q;
//==================================================================================================
//Rung 22 :
//==================================================================================================
R[16] = (R[9] && R[16] && !B[6]) || ( R[9] && !R[16] && B[6]);
//==================================================================================================
//Rung 23 : SDP 1/2
//==================================================================================================
OSR_dat[6].IN = (R[13] && B[7]); OSR(OSR_dat[6]); B[7] = OSR_dat[6].Q;
//==================================================================================================
//Rung 24 :
//==================================================================================================
R[17] = (R[13] && B[7] && !R[17]) || ( R[13] && !B[7] && R[17]);
//==================================================================================================
//Rung 25 : SDP 2/2
//==================================================================================================
OSR_dat[7].IN = (R[13] && B[8]); OSR(OSR_dat[7]); B[8] = OSR_dat[7].Q;
//==================================================================================================
//Rung 26 :
//==================================================================================================
R[18] = (R[13] && !B[8] && R[18]) || ( R[13] && !R[18] && B[8]);
//==================================================================================================
//Rung 27 : SDP3/2
//==================================================================================================
OSR_dat[8].IN = (R[13] && B[9]); OSR(OSR_dat[8]); B[9] = OSR_dat[8].Q;
//==================================================================================================
//Rung 28 :
//==================================================================================================
R[19] = (R[13] && !B[9] && R[19]) || ( R[13] && !R[19] && B[9]);
//==================================================================================================
}
//----------------------------------User Defined Function Stage 1-----------------------------------
//----------------------------------User Defined Function Stage 2-----------------------------------
//----------------------------------User Defined Function Stage 2-----------------------------------
//----------------------------------User Defined Function Stage 3-----------------------------------
//----------------------------------User Defined Function Stage 3-----------------------------------
//----------------------------------Testing Function------------------------------------------------
void Switch_to_Bit() {
//this function only for simulation B variable with Switch
//B[1] = S[7];
B[2] = S[8];
B[3] = S[9];
B[4] = S[10];
B[5] = S[11];
B[6] = S[12];
B[7] = S[13];
B[8] = S[14];
B[9] = S[15];
}
void DI_print() {
for (int i = 0; i < numButtons; i++) {
Serial.print(S[i]);
}
Serial.println("");
}
//----------------------------------Testing Function------------------------------------------------
//--------------------------------- Setup-----------------------------------------------------------
void setup() {
Serial.begin(115200);
// Initialize push button pins as inputs
for (int i = 0; i < numButtons; i++) {
pinMode(buttonPins[i], INPUT); //
}
// Initialize relay pins as outputs
for (int i = 0; i < numRelays; i++) {
pinMode(relayPins[i], OUTPUT);
digitalWrite(relayPins[i], HIGH); // Turn off all relays initially Active Low
}
//Start Services
lcd1.init ();
lcd1.backlight ();
lcd1.clear();
//Timer Initialization (dalam detik)
T[1].PT = 5; //
T[2].PT = 5; //
T[3].PT = 5; //
T[4].PT = 5; //
T[5].PT = 5; //
T[6].PT = 5; //
T[7].PT = 5;
T[8].PT = 1; //timer switch display
CTU_data[1].PV = 2; //percobaan cranking sebelum alarm
spwm_dat[1].PT_on = 5; // in seconds (ON time)
spwm_dat[1].PT_off = 5; // in seconds (OFF time)
spwm_dat[2].PT_on = 10; // in seconds (ON time)
spwm_dat[2].PT_off = 5; // in seconds (OFF time)
spwm_dat[3].PT_on = 15; // in seconds (ON time)
spwm_dat[3].PT_off = 5; // in seconds (OFF time)
}
//Testing
//--------------------------------- Setup-----------------------------------------------------------
//--------------------------------- Loop------------------------------------------------------------
void loop() {
unsigned long startTime;
delay(10); // this speeds up the simulation
//Input Update
DI_Update();
Switch_to_Bit(); //simulaton
LCD1_Update(R, R_P, 13);
//blynking_LCD();
//Process
ATS_System();
DI_print();
//Update Output
DO_Update();
}
//--------------------------------- Loop------------------------------------------------------------
//--------------------------------- Note------------------------------------------------------------
/*
I/O Maps
INPUT
S1 : MCB PLN
S2 : SW LVMDP 1
S3 : SW LVMDP 2
S4 : EMERGENZY
S5 : MCB 12 VOLT
S6 : OUT GENZED 220 V
Output
R1 : KONTAKTOR PLN
R2 : OFF GENSED
R3 : CRENGKING 1
R4 : CRENGKING 2
R5 : CRENG 3
R6 : KONTAKTOR GENZED
R7 : ALARM GAGAL STAR
R8 : OUT ATS
R9 : LVMDP1
R10 : OUT CRENG 1/220 V
R11 : OUT CRENG 2 220 V
R12 : OUT CRENG 3/220 V
R13 : LVMDP 2
R14 : SDP 1/1
R15 : SDP 2/1
R16 : SDP 3/1
R17 : SDP 1/2
R18 : SDP 2/2
R19 : SDP 13/2
R20 : EMERGENZY
Binary
B1 :
B2 : LVMDP 1 ON/OFF
B3 : LVMDP 2 ON/OFF
B4 : PB SDP 1/1
B5 : PB SDP 2/1
B6 : PB SDP 3/1
B7 : PB SDP 1/2
B8 : PB SDP 2/2
B9 : PB SDP 3/2
*/
//--------------------------------- Note------------------------------------------------------------