// ************** Bibliotheken **************
#include <Wire.h> // Wire Bibliothek einbinden
#include <LiquidCrystal_I2C.h> // Vorher hinzugefügte LiquidCrystal_I2C Bibliothek einbinden
#include "DHT.h" //DHT Bibliothek laden
// ************** Structs **************
struct Joystick { // Struct für die Funktion zum Erkennen des Joystick-Status
bool left, right, up, down, push;
const int button_pin; // Digitale Eingangsklemme Taster
const int x_pin; // Analoge Eingangsklemme Joystick x-Achse (hoch/runter)
const int y_pin; // Analoge Eingangsklemme Joystick y-Achse (links/rechts)
};
struct Plant { // Struct für die Pflanzen -> Bewässerung wird skalierbar
String name; // Name der Pflanze
int soil_moisture_int; // Aktuelle Bodenfeuchte [INT]
int soil_moisture_percent; // Aktuelle Bodenfeuchte [%]
int pin_soil; // Pin angeschlossener Sensor
int ambient_temperature; // Umgebungstemperatur
int ambient_humidity; // Luftfeuchte
bool pump_on_auto; // Pumpe anschalten (Automatikbetrieb)
bool pump_on_hand; // Pumpe anschalten (Handbetrieb)
bool pump_release; // Pumpe freigegeben
int pin_pump; // Pin angeschlossene Pumpe (Ausgang)
int pump_auto_cycle; // Automatischer Gießzyklus Status
int number_water_cycles; // Anzahl der durchgeführten Gießvorgänge im aktuellen Gießzyklus
int limit_ll_watercycle, limit_ul_watercycle; // lower-limit = Wert ab dem Gießzyklus endet, upper-limit = Wert ab dem Gießzyklus startet
int limit_ll_watercycle_norm, limit_ul_watercycle_norm; // Limit s.O. auf 0 - 100% normiert - ACHTUNG!! 0% = trocken, 100% = nass -> vertauschte Normierung
int humidity_sensor_ll, humidity_sensor_ul; // upper-limit und lower-limit (Int-Wert) des Bodenfeuchtesensors (upper = trocken, lower = nass) - Baubedingte Sensorgrenzen
int limit_number_water_cycles; // Maximale Anzahl an Wiederholungen im Gießzyklus bis zur Fehlermeldung
bool stoerungen[8]; // Binär codierte Störungen - Bit: 0 = Gießzyklen; 1 = Wasserstand Behälter; 2 = ...
};
struct Timer { // Struct für Timer
unsigned long previousMillis = millis(); // vorherige Zeit - Initialisierung mit dem aktuellen Zeitwert
};
struct PageProperties { // Struct für das aktuelle Level und Zeile der LCD-Menüstruktur
int level;
int row_l1;
int row_l2;
int row_l3;
};
struct DHTvalues { // Struct für das aktuelle Level und Zeile der LCD-Menüstruktur
int temperature;
int humidity;
};
// ************** Globale Variablen **************
const int js_panel_button_pin = 2; // Ausgangsklemme Digitaler Ausgang Pumpe 1
const int pump1_pin = 3; // Ausgangsklemme Digitaler Ausgang Pumpe 1
const int LED_red_pin = 5; // Ausgangsklemme der LED (rot) zum Anzeigen eines aufgetretenen Fehlers
const int LED_yellow_pin = 6; // Ausgangsklemme der LED (geln) zum Anzeigen ...
const int LED_green_pin = 7; // Ausgangsklemme der LED (grün) zum Anzeigen ...
const int js_panel_y_pin = A0; // Analoge Eingangsklemme Joystick Panel y-Achse
const int js_panel_x_pin = A1; // Analoge Eingangsklemme Joystick Panel x-Achse
const int soil_sensor1_pin = A2; // Analoge Eingangsklemme Bodenfeuchtesensor 1
const int water_level_sensor_pin = A3; // Analoge Eingangsklemme Sensor Füllstand
DHT dht(4, DHT22); // DHT11 Sensor angeschlossen an Pin2
LiquidCrystal_I2C lcd(0x27, 16, 2); //Hier wird festgelegt um was für einen Display es sich handelt. In diesem Fall eines mit 16 Zeichen in 2 Zeilen und der HEX-Adresse 0x27.
LiquidCrystal_I2C lcd2(0x28, 16, 2); //Hier wird festgelegt um was für einen Display es sich handelt. In diesem Fall eines mit 16 Zeichen in 2 Zeilen und der HEX-Adresse 0x28.
Joystick js_panel = {false, false, false, false, false, js_panel_button_pin, js_panel_y_pin, js_panel_x_pin}; // Globale Variable Status des Joysticks an dem LCD Panel
// *****************************************************************
// ***************************** Setup *****************************
// *****************************************************************
void setup() {
// ************** Initialisierung **************
lcd.init(); //Im Setup wird der LCD gestartet
lcd.backlight(); // Hintergrundbeleuchtung einschalten (lcd.noBacklight(); schaltet die Beleuchtung aus).
lcd2.init();
lcd2.backlight();
pinMode(js_panel.button_pin, INPUT_PULLUP); // Joystick Signal als Digitaler Eingang (INPUT)
pinMode(pump1_pin, OUTPUT); // Pumpe 1 Ansteuerung als Digitaler Ausgang
pinMode(LED_red_pin, OUTPUT); // LED Rot Ansteuerung als Digitaler Ausgang
pinMode(LED_yellow_pin, OUTPUT); // LED Gelb Ansteuerung als Digitaler Ausgang
pinMode(LED_green_pin, OUTPUT); // LED Grün Ansteuerung als Digitaler Ausgang
dht.begin(); // Initialisierung des DHT-Sensors
// ************** Start Funktionen **************
while (js_panel.push == false) {
joystick_movement(js_panel);
startup_text();
}
}
// ****************************************************************
// ***************************** Loop *****************************
// ****************************************************************
void loop() {
// ************************* Variablendeklarationen *************************
static Plant plant1 {"Efeutute", 0, 0, soil_sensor1_pin, 0, 0, 0, 0, 0, pump1_pin, 0, 0, 285, 397,
map(plant1.limit_ll_watercycle, plant1.humidity_sensor_ul, plant1.humidity_sensor_ll, 100, 0),
map(plant1.limit_ul_watercycle, plant1.humidity_sensor_ul, plant1.humidity_sensor_ll, 100, 0), 204, 530, 5}; // Variable für Pflanze 1
static Timer t1, t2, t3, t4, t5; // Timer Sensoren Pflanze 1
static DHTvalues dht1; // Messwerte des Temperatur- und Luftfeuchtesensors 1
static bool yellow_LED; // Variable Ansteuerung gelbe LED
// ************************* Sensoren einlesen *************************
dht1 = read_dht(dht, t3); // Temperatur- und Luftfeuchtesensor für beide Pflanzen einlesen
read_plant_sensors(plant1, t1, dht1); // Einlesen aller Sensoren der Pflanze 1
// ************************* Ausführung Programm *************************
joystick_movement(js_panel); // Einlesen des Joysticks am LCD-Panel
page_management(plant1); // LCD-Menüstruktur
display2(plant1);
water_cycle(plant1, t4, t5, yellow_LED); // Automatischer Gießzyklus Pflanze 1
// ************************* Ausgänge schreiben *************************
bool fault_detected_plant1 = fault_detected(plant1.stoerungen); // fault_release = Eine Störung liegt bei der Pflanze vor
digitalWrite(LED_red_pin, fault_detected_plant1); // Störung an Pflanze liegt vor -> Fehler LED (rot) leuchtet
digitalWrite(LED_yellow_pin, yellow_LED); // Ansteuerung gelbe LED
pump_control(plant1.pin_pump, LED_green_pin, plant1.pump_on_auto, plant1.pump_on_hand, fault_detected_plant1); // Ansteuerung der Pumpe für Pflanze 1
}
void water_cycle(Plant &p, Timer &t1, Timer &t2, bool &yellow_LED) { // Funktion für das automatische Gießen einer Pflanze
if (p.pump_release == 1) { // Automatisches Gießen darf nur Anlaufen, wenn im Display die Pumpe aktiviert wurde
if (p.soil_moisture_percent < p.limit_ll_watercycle_norm && p.pump_auto_cycle == 0) { // Bodenfeuchte < Grenzwert am dem gegossen werden soll (und Zeit vergangen) & Der Gießzyklus ist nicht aktiv (0)
if (time_passed(t1, 5000) == 1) { // Zeit, wie lange der Grenzwert der Bodenfeuchte unterschritten sein muss
p.pump_auto_cycle = 1;
} else {
yellow_LED = blink_interval(t2, 500, yellow_LED);
}
} else {
t1.previousMillis = millis();
yellow_LED = 0;
}
if (p.pump_auto_cycle >= 1) {
yellow_LED = 1;
switch (p.pump_auto_cycle) {
case 1: // Initialschritt - größere Wassermenge als in den verbleibenden Schritten zuführen
/* !! TODO !! */
break;
case 2: // Warteschritt - Weiterleitung zum nächsten Gießzyklus oder zum Ende
/* !! TODO !! */
break;
case 3: // Überprüfung ob die max Anzahl an Gießzyklen erreicht wurde
/* !! TODO !! */
break;
case 4: // Gießschritt - gießt während der eingestellten Zeit
/* !! TODO !! */
break;
case 5: // Ende des Gießzyklus
p.pump_auto_cycle = 0;
yellow_LED = 0;
p.number_water_cycles = 0;
break;
case 6: // Fehler - zu viele Gießzyklen benötigt
p.stoerungen[0] = 1;
break;
}
}
} else { // Gießzyklus ist nicht aktiv
p.pump_auto_cycle = 0;
p.pump_on_auto = 0;
yellow_LED = 0;
t1.previousMillis = millis();
}
}
// !!! Zeilen ab hier für die Aufgabe nicht benötigt !!!
// **********************************************************************
// ***************************** Funktionen *****************************
// **********************************************************************
// ************** LCD **************
void startup_text() { // Funktion für die Anzeige des Start-Textes
static Timer t1; // Initialisierung Timer für zeitgesteuerte Umschaltung
static int counter = 0;
const String text[] = {
"Aufgabe MCT ",
"Studienarbeit ",
"Autom. Pflanzen-",
"bewaesserung ",
"Zum Starten ",
"druecken "
};
if (counter == 6) {
counter = 0;
}
if (time_passed(t1, 3000) == 1) {
display2StringsOnLCD(text[counter], text[counter + 1]);
counter = counter + 2;
}
}
void display2StringsOnLCD(String row1, String row2) { // Funktion zum Ausgeben von 2 Zeichenketten auf dem LCD-Display
lcd.setCursor(0, 0); // Position des ersten Zeichens in der ersten Zeile
lcd.print(row1); // Ausgabe erste Zeile
lcd.setCursor(0, 1); // Position des ersten Zeichens in der zweiten Zeile
lcd.print(row2); // Ausgabe zweite Zeile
}
void display2StringsOnLCD_arrow(const String text[], int row) { // Funktion zum Ausgeben der Menüauswahlpunkte (LCD-Display) mit vorangestelltem Pfeil
String text1, text2;
int first_row = floor((row + 1) / 2) * 2 - 1; // Berechnung erste angezeigte Reihe -> Bei Reihe 1 & 2 muss 1 die erste sein, bei 3 & 4 die 3, usw.
if (row % 2 == 1) { // Bei der Auswahl einer ungeraden Reihe muss der Pfeil in der ersten Reihe sein, bei einer gerade in der zweiten
text1 = ">" + text[first_row - 1];
text2 = " " + text[first_row];
} else {
text1 = " " + text[first_row - 1];
text2 = ">" + text[first_row];
}
display2StringsOnLCD(text1, text2);
}
void page_start(int row) { // Funktion zur Darstellung der Startseite
const String text[] = { // Array für die auszugebenden Texte
"Pflanze 1 ",
"System ",
};
display2StringsOnLCD_arrow(text, row);
}
void page_plant_overview(int row) { // Funktion zur Darstellung der Startseite
const String text[] = { // Array für die auszugebenden Texte
"Pumpe ",
"Bodenfeuchte ",
"Umgebungsluft ",
"Grenzwerte ",
" "
};
display2StringsOnLCD_arrow(text, row);
}
void page_system(int row) { // Funktion zur Darstellung der System Seite
const String text[] = { // Array für die auszugebenden Texte
"Stoerungen ",
"Info ",
" "
};
display2StringsOnLCD_arrow(text, row);
}
void page_plant_pump(int row, Joystick js, Plant &p) { // Funktion zur Darstellung der Pumpen Seite einer Pflanze
static bool hm[2];
static String text[] = { // Array für die auszugebenden Texte
"Aktiv: nein ",
"Handbetrieb ",
" "
};
if (row == 1 && edge_detection(js.push, hm[0]) == 1) { // Umschaltung Pumpe aktiv ja/nein
if (hm[1] == 0) {
text[0] = "Aktiv: ja ";
hm[1] = 1;
} else {
text[0] = "Aktiv: nein ";
hm[1] = 0;
}
}
if (row == 2 && js.push == 1) {
p.pump_on_hand = 1; // Pumpe über Auswahl "Handbetrieb" aktivieren
} else {
p.pump_on_hand = 0;
}
display2StringsOnLCD_arrow(text, row);
p.pump_release = hm[1]; // 1 = Pumpe aktiv
}
void page_plant_soil_moisture(int row, Plant plant) { // Funktion zur Darstellung der Bodenfeuchte Seite einer Pflanze
String tempString;
String text[] = { // Array für die auszugebenden Texte
">Prozent: ",
" min 0% max 100%",
">Int-Wert: ",
"", // Grenzen des verwendeten Sensors
};
text[0] = text[0] + String(plant.soil_moisture_percent) + "% ";
text[2] = text[2] + String(plant.soil_moisture_int) + " ";
text[3] = " Min " + String(plant.humidity_sensor_ll) + " Max " + String(plant.humidity_sensor_ul) + " ";
if (row == 1) {
display2StringsOnLCD(text[0], text[1]);
}
if (row == 2) {
display2StringsOnLCD(text[2], text[3]);
}
}
void page_plant_environment(int row, Plant plant) { // Funktion zur Darstellung der Umgebunsluft-Werte einer Pflanze
const String text[] = { // Array für die auszugebenden Texte
">Feuchte: ",
">Temp.: ",
};
text[0] = text[0] + String(plant.ambient_humidity) + "% ";
text[1] = text[1] + String(int(plant.ambient_temperature)) + "\xdf" + "C ";
display2StringsOnLCD(text[0], text[1]);
}
void page_plant_limits(int row, Plant plant) { // Funktion zur Darstellung der Grenzwerte einer Pflanze
String text[] = { // Array für die auszugebenden Texte
">Autom. Giessen ",
"", // min - max
};
text[1] = " UG: " + String(plant.limit_ll_watercycle_norm) + "% OG: " + String(plant.limit_ul_watercycle_norm) + "% ";
if (row == 1) {
display2StringsOnLCD(text[0], text[1]);
}
}
void page_system_faults(int row, Plant p1) { // Funktion zur Darstellung der Störungen im System
String text[] = { // Array für die auszugebenden Texte
">Pflanze 1 ",
" Code: ",
};
text[1] = text[1] + intlist_to_string(p1.stoerungen) + " ";
if (row == 1) {
display2StringsOnLCD(text[0], text[1]);
}
}
void page_system_info(int row) { // Funktion zur Darstellung der Informationen über das System
String text[] = { // Array für die auszugebenden Texte
">Version 1.0 ",
" 15.12.2023 ",
};
display2StringsOnLCD(text[0], text[1]);
}
void page_management(Plant &p1) { // Funktion zur Verwaltung der Seiten des LCD-Displays - alle zuvor deklarierten "page" Funktionen werden hier verwendet
static Timer t1; // Timer - Aktualisierungsrate Display
static PageProperties menu{1, 1, 1, 1}; // Erstellung Struct für die aktuellen Seiteneigenschaften des Menüs
if (time_passed(t1, 100) == 1) { // Seiten max. alle xxx ms aktualisieren
if (menu.level == 1) { // Startseite
page_start(menu.row_l1);
page_movement(js_panel, menu, 2);
menu.row_l2 = 1;
menu.row_l3 = 1;
}
if (menu.level == 2 && menu.row_l1 == 1) { // Pflanze 1
page_plant_overview(menu.row_l2);
page_movement(js_panel, menu, 4);
menu.row_l3 = 1;
}
if (menu.level == 3 && menu.row_l2 == 1 && menu.row_l1 == 1) { // Pflanze 1 - Pumpe
page_plant_pump(menu.row_l3, js_panel, p1);
page_movement(js_panel, menu, 2);
}
if (menu.level == 3 && menu.row_l2 == 2 && menu.row_l1 == 1) { // Pflanze 1 - Bodenfeuchte
page_plant_soil_moisture(menu.row_l3, p1);
page_movement(js_panel, menu, 2);
}
if (menu.level == 3 && menu.row_l2 == 3 && menu.row_l1 == 1) { // Pflanze 1 - Umgebungsluft
page_plant_environment(menu.row_l3, p1);
page_movement(js_panel, menu, 2);
}
if (menu.level == 3 && menu.row_l2 == 4 && menu.row_l1 == 1) { // Pflanze 1 - Grenzwerte
page_plant_limits(menu.row_l3, p1);
page_movement(js_panel, menu, 3);
}
if (menu.level == 2 && menu.row_l1 == 2) { // System
page_system(menu.row_l2);
page_movement(js_panel, menu, 2);
menu.row_l3 = 1;
}
if (menu.level == 3 && menu.row_l2 == 1 && menu.row_l1 == 2) { // System - Stoerungen (Binär codiert)
page_system_faults(menu.row_l3, p1);
page_movement(js_panel, menu, 1);
}
if (menu.level == 3 && menu.row_l2 == 2 && menu.row_l1 == 2) { // System - Info
page_system_info(menu.row_l3);
page_movement(js_panel, menu, 1);
}
}
}
void page_movement(Joystick js_selection, PageProperties &pp, int max_row) { // Funktion zur Navigation zwischen den Seiten des Displays
static bool hm[5];
if (edge_detection(js_selection.down, hm[0]) == 1) {
switch (pp.level) {
case 1:
pp.row_l1++;
if (pp.row_l1 > max_row) {
pp.row_l1 = 1;
}
break;
case 2:
pp.row_l2++;
if (pp.row_l2 > max_row) {
pp.row_l2 = 1;
}
break;
case 3:
pp.row_l3++;
if (pp.row_l3 > max_row) {
pp.row_l3 = 1;
}
break;
}
}
if (edge_detection(js_selection.up, hm[1]) == 1) {
switch (pp.level) {
case 1:
pp.row_l1--;
if (pp.row_l1 == 0) {
pp.row_l1 = 1; // row_l1 darf nicht 0 oder negativ werden
}
break;
case 2:
pp.row_l2--;
if (pp.row_l2 == 0) {
pp.row_l2 = 1; // row_l1 darf nicht 0 oder negativ werden
}
break;
case 3:
pp.row_l3--;
if (pp.row_l3 == 0) {
pp.row_l3 = 1; // row_l1 darf nicht 0 oder negativ werden
}
break;
}
}
if (edge_detection(js_selection.right, hm[3]) == 1) { // edge_detection(js_selection.push, hm[2]) == 1 ||
pp.level++;
if (pp.level > 3) {
pp.level = 3; // Max. Level = 3 (siehe Struktur Ausarbeitung)
}
}
if (edge_detection(js_selection.left, hm[4]) == 1) {
pp.level--;
if (pp.level == 0) {
pp.level = 1; // level darf nicht 0 oder negativ werden
}
}
}
// ************** Sensoren / Aktoren **************
void joystick_movement(Joystick &js) { // Funktion zur Nutzung eines Joysticks als Steuerkreuz
int joystick_x = analogRead(js.x_pin); // Einlesen x-Achse Joystick (hoch/runter)
int joystick_y = analogRead(js.y_pin); // Einlesen y-Achse Joystick (links/rechts)
js.push = !digitalRead(js.button_pin); // Einlesen des Joystick Taster, ! = Negierung
if (joystick_x < 40) {
js.down = true;
} else {
js.down = false;
}
if (joystick_x > 850) {
js.up = true;
} else {
js.up = false;
}
if (joystick_y > 850) {
js.left = true;
} else {
js.left = false;
}
if (joystick_y < 40) {
js.right = true;
} else {
js.right = false;
}
}
bool edge_detection(bool signal, bool &previousState) { // Funktion zur Erkennung einer positiven Flanke
if (signal == HIGH && previousState == LOW) {
previousState = HIGH;
return true;
}
previousState = signal;
return false;
}
bool pump_control(int pin_pump, int pin_LED, bool start1, bool start2, bool fault) { // Funktion zur Ansteuerung einer Pumpe und LED
if (!fault && (start1 || start2)) {
digitalWrite(pin_pump, true);
digitalWrite(pin_LED, true);
} else {
digitalWrite(pin_pump, false);
digitalWrite(pin_LED, false);
}
}
void read_plant_sensors(Plant &p, Timer &t, DHTvalues dht_sensor) { // Funktion zum Auslesen der Sensoren einer Pflanze - Bodenfeuchte und Umgebungstemperatur und -luftfeuchtigkeit
if (time_passed(t, 500) == 1) { // Sensoren nur alle xxx ms einlesen
p.soil_moisture_int = humidity_simulation(p);
p.soil_moisture_percent = map(p.soil_moisture_int, p.humidity_sensor_ul, p.humidity_sensor_ll, 0, 100); // Inverse Zuordnung - ll = nass, ul = trocken
}
p.ambient_temperature = dht_sensor.temperature;
p.ambient_humidity = dht_sensor.humidity;
}
int humidity_simulation(Plant p) { // Funktion zur Simulation der Bodenfeuchte einer Pflanze
static int humidity = 370;
static bool hm;
static Timer t1, t2;
switch (hm) {
case 0:
hm = 1;
t1.previousMillis = millis();
break;
case 1:
if (time_passed(t1, 100) == 1) {
hm = 0; // alle 100 ms integrieren
}
break;
}
if (!p.pump_on_auto && !p.pump_on_hand) {
if (hm == 0) {
humidity += random(1,5);
if (humidity > p.humidity_sensor_ul) {
humidity = p.humidity_sensor_ul;
}
}
return humidity;
} else {
if (hm == 0) {
humidity -= 10;
if (humidity < p.humidity_sensor_ll) {
humidity = p.humidity_sensor_ll;
}
}
}
}
void display2(Plant plant) {
String text[] = { // Array für die auszugebenden Texte
">Prozent: ",
"",
};
text[0] = text[0] + String(plant.soil_moisture_percent) + "% ";
text[1] = "UG: " + String(plant.limit_ll_watercycle_norm) + "% OG: " + String(plant.limit_ul_watercycle_norm) + "%";
lcd2.setCursor(0, 0); // Position des ersten Zeichens in der ersten Zeile
lcd2.print(text[0]); // Ausgabe erste Zeile
lcd2.setCursor(0, 1); // Position des ersten Zeichens in der zweiten Zeile
lcd2.print(text[1]); // Ausgabe zweite Zeile
}
DHTvalues read_dht(DHT sensor, Timer &t) { // Funktion zum Auslesen des DHT Sensor und schreiben in das hierfür erstellte Struct "DHTvalues"
if (time_passed(t, 2000) == 1) { // Sensor nur alle xxx ms einlesen
DHTvalues temp; // Temporäres Objekt
temp.temperature = sensor.readTemperature();
temp.humidity = sensor.readHumidity();
return temp;
}
}
bool fault_detected(bool array1[]) { // Funktion zum Erkennen eines Fehlers im Störungen Array einer Pflanze
for (int i = 0; i < 8; ++i) {
if (array1[i] == 1) {
return 1; // Verlasse die Funktion, da der Ausgang bereits gesteuert wurde
}
}
return 0;
}
bool blink_interval(Timer &t, int interval, bool output) { // Funktion zur Ausführung einer Blink-Funktion in Abhängigkeit eines Intervals
if (time_passed(t, interval)) {
if (output == HIGH) { // Wechselt den Zustand des Ausgangs (ein/aus)
return 0;
} else {
return 1;
}
}
}
// ************** Allgemein **************
String intlist_to_string(bool list[]) { // Funktion um die Einträge eines 1 Byte großen bool Arrays in einen String zu schreiben
String temp = "";
for (int i = 7; i >= 0 ; i--) {
temp += String(list[i]);
}
return temp;
}
bool time_passed(Timer &t, unsigned long interval) { // Timer Funktion für Zeitsteuerungen
unsigned long currentMillis = millis(); // Aktuelle Zeit seit Start Arduino [ms]
if (currentMillis < t.previousMillis) {
t.previousMillis = currentMillis; // Überlauf Behandlung
}
if (currentMillis - t.previousMillis >= interval) {
t.previousMillis = currentMillis;
return true;
}
return false;
}
bool timer_pulse(unsigned long interval) { // Erzeugt ein dauerhaftes "1" Signal, welches nach Ablauf des Intervals für einen Zyklus unterbrochen wird / darf nicht parallel aufgerufen werden!
static Timer t;
static bool output;
switch (output) {
case 0:
output = 1;
t.previousMillis = millis();
break;
case 1:
if (time_passed(t, interval) == 1) {
output = 0;
}
break;
}
return output;
}
// Created by Manuel S. TEL21AT1 DHBW Mannheim