Zwei Taster für eine LED

So ist es wohl.

Auch, damit die fetten Schütze für den 180KW Drehstrommotor, von der Hammermühle, nicht unnütz schnattern. Denn das machen sie nicht lange mit.


Nagut, hier mal eine "Tiefgarage"...
Das hinzufügen/wegnehmen von weiteren Tastern sollte offensichtlich sein
Auch wie man sie dem Treppenhaus oder der Tiefgarage zuordnet.
Die Zähler werden hier nur benutzt um Ereignisse zu produzieren, war grade kein anderer Eventspender zur Hand.

Jeder Taster hat seine eigene Entprellung und Flankenerkennung.
Das Treppenhaus hat eine (einstellbare) Leuchtzeit und geht danach aus, wenn nicht zwischen durch wieder getriggert wird.
Vorbild: Treppenlichtautomat

In der Tiefgarage kann man von jeder Stelle das Licht ein und aus Tasten.
Vorbild: Eltako Stromstoßrelais

#include <CombiePin.h>
#include <CombieTimer.h>
#include <CombieTools.h>
#include <CombieTypeMangling.h>

using namespace Combie::Millis;
using Counter = Combie::Tools::Counter<byte>;


class Task
{
  public:
  virtual void init() = 0;
  virtual void run()  = 0;
};


template<byte tasterPin>class TasterAction : public Task
{
   private:
   Counter &c;
   Combie::Pin::InvInputPin<tasterPin> t;
   Combie::Timer::EntprellTimer        e{20_ms};
   Combie::Tools::FlankenErkennung     f;
   
   public:
   TasterAction(Counter &counter) : c(counter){};
   virtual void init() override  {  t.initPullup();   }
   virtual void run()  override  {  c = f = e = t;    }
};

Combie::Pin::OutputPin<12>  tiefgaragenlicht;
Counter tiefgarage;

//Combie::Timer::FallingEdgeTimer nachleuchten {1_Minute}; 
Combie::Timer::FallingEdgeTimer nachleuchten {2.5_Sekunden}; 
Combie::Pin::OutputPin<11>  treppenhauslicht;
Counter treppenhaus;

Task *tasks[] {
                new TasterAction<2>{treppenhaus},
                new TasterAction<3>{tiefgarage},
                new TasterAction<4>{treppenhaus},
                new TasterAction<5>{tiefgarage},
                new TasterAction<6>{tiefgarage},
                new TasterAction<7>{treppenhaus},
              };  

void setup() 
{
  tiefgaragenlicht.init();
  treppenhauslicht.init();
  for(Task *t:tasks) t->init();
  tiefgarage.onCount ([](Counter&){tiefgaragenlicht.toggle();});
  treppenhaus.onCount([](Counter&){nachleuchten=1;});
}

void loop() 
{  
  for(Task *t:tasks) t->run();  
  treppenhauslicht = nachleuchten = 0;
}

CombieLib.zip (379,2 KB)

Combie::Tools fehlt leider.
Ansonsten werden die Counter eigentlich gar nicht wirklich (nur für den onCount callback) gebraucht: der eine OutputPin wird durch .toggle(), der andere durch einen FallingEdgeTimer gesteuert.

Und dass man keine 6 TasterAction sondern nur 2 Relais braucht, sollte sogar jeder Elektriker merken, und die 6 Taster auf 2 Eingänge parallel schalten.
Und auch das Tiefgaragenlicht sollte irgendwann ausgehen, wenn es keiner vorher ausschaltet.
Gibt's sowas, oder braucht ein Elektriker dafür ein 3. Relais?

Aber sonst eine schöne Demo, ehrlich.

Dass man in loop noch zwei Zeilen braucht, ist eigentlich unschön, oder? :slight_smile:
Ich würde vorschlagen, du erfindest Combie::Tools::CombieLoop, in der man bei der Definition der Objekte alle loop-Funktionen als callbacks einhängen kann und hast eine
  void loop() {CombieLoop::run();}
die du irgendwo versteckst, oder dir fällt noch was besseres ein.

Korrigiert!

Das ist die Haushalts spar Schaltung.
In der Industrie und Militär, oder im Flugzeugbau, wird man das nicht tun.
Denn es erschwert die Diagnose und das gezielte abschalten einzelner "Zweige"

Das bräuchte zusätzlich Präsensmelder, um dem Hausmeister Bescheid zu geben, wenn da mal wieder einer Übernachtet, oder eine Party gefeiert wird.

Die eine Zeile kann man gerne noch in eine eigene Task stopfen.
Schien mir aber doch etwas überzogen zu sein....
2 Zeilen in Loop ist jetzt wirklich noch nicht so dolle übertreiben.
Ab 20, da würde ich ernsthaft Entscheidungen treffen wollen.

Ich danke dir für das Blümlein!

Immer wieder sehe ich hier, wie sich Leute in einem if-else Gewirr verstricken.
Quasi täglich. Auch gerne mehrmals am Tag.
Vielleicht sogar noch ein paar eingeflochtene Schleifen und mit ein paar goto garniert.

Nur weil man begriffen hat, wie ein Hammer funktioniert, muss doch nicht die ganze Welt plötzlich aus Nägeln bestehen.

Ich selber bin (auch) nicht gut darin tiefe Verschachtelungen mit komplizierten Bedingungen zu überblicken. Wenn es dann wirklich sein muss, arbeite ich über Wahrheitstabellen, um sowas realisieren zu können. Den Schritt lassen viele aus, oder kennen ihn noch nicht einmal.
Meine Grenze der intuitiven Erfassung von If-Kaskaden liegt bei ca 3 Ebenen mit einfachen Bedingungen. Danach: Overload.

Das Konzept lautet:
Möglichst alles in. gut getestete, Komponenten stopfen.
Einfache Schnittstellen
Flache Programmstruktur

Die CooperativeTask Dinger....
Das ist die etwas ausgewachsenere Variante der Tasks.

void loop() {Scheduler::instance().run();}

Hallo night_wave

Ich will einen Sketch veröffentlichen in dem die Datenstrukturen offen ersichtlich sind und nicht in ZIP-Files versteckt werden.
Viel Spass beim Testen, Studieren, Ausprobieren und Spielen:

#define usl unsigned long  // ich bin Tippfaul :)
// make variables
constexpr int ButtonPins[] {A0};
constexpr int LedPin {9};
constexpre usl Verzoegerungszeit {3000};
// make structures

struct TIMER
{
  usl interval;
  usl stamp;
  int onOff;
  void make (usl interval_)
  {
    interval = interval_;
  }
  void start(usl currentTime)
  {
    stamp = currentTime;
    onOff = HIGH;
  }
  int run(usl currentTime)
  {
    int returnValue = 0;
    if (currentTime - stamp >= interval && onOff)
    {
      onOff = LOW;
      returnValue = HIGH;
    }
    return returnValue;
  }
} timer;

struct STAIRCASELIGHT
{
  int pin;
  int oldState;
  usl debounceStamp;
  usl debounceTime;
  void make(int pin_, int pinmode, usl debounceTime_)
  {
    pin = pin_;
    pinMode(pin, pinmode);
    debounceTime = debounceTime_;
  }
  int run(usl currentTime)
  {
    int returnValue = LOW;
    if (currentTime - debounceStamp >= debounceTime)
    {
      debounceStamp = currentTime;
      int newState = digitalRead(pin) ? LOW : HIGH;
      if (oldState != newState)
      {
        oldState = newState;
        if (newState == HIGH) returnValue = HIGH;
      }
    }
    return returnValue;
  }
};
STAIRCASELIGHT stairCaseLights[sizeof(ButtonPins) / sizeof(ButtonPins[0])];
// tools
void heartBeat(int LedPin)
{
  static bool setUp = false;
  if (!setUp) pinMode (LedPin, OUTPUT), setUp = !setUp;
  digitalWrite(LedPin, (millis() / 1000) % 2);
}
void setup()
{
  pinMode(LedPin, OUTPUT);
  int element = 0;
  for (auto &stairCaseLight : stairCaseLights) {
    stairCaseLight.make(ButtonPins[element], INPUT_PULLUP, 20);
    element++;
  }
  timer.make(Verzoegerungszeit);
}
void loop()
{
  usl currentMillis = millis();
  heartBeat(LED_BUILTIN);
  for (auto &stairCaseLight : stairCaseLights)
  {
    if (stairCaseLight.run(currentMillis) == HIGH)
    {
      digitalWrite(LedPin, digitalRead(LedPin) ? LOW : HIGH);
      timer.start(currentMillis);
    }
  }
  if (timer.run(currentMillis) == HIGH) digitalWrite(LedPin, LOW);
}

Ich wünsche einen geschmeidigen Abend und viel Spass beim Programmieren in C++.

p.s. Der Verzögerungstimer für das automatische Auschalten ist auf 3 Sekunden gesetzt und kann im Sketch geändert werden.

Auch auf die Gefahr hin, das mir das um die Ohren fliegt, was ich jetzt schreibe, aber ich versuchs mal.

Nein.
Um die Verwirrung perfekt zu machen: Aus dem Datenblatt.

Aber versuch einer Auflösung :wink:
Der digitale Eingang des AVR interpretiert alles(*) was bis 0,3*Versorgungsspannung ist als niedriger Pegel und alles was über 0,6*Versorgungsspannung ist als hohen Pegel.
Mehr kann er nicht.
Mit jedem Pegelwechsel kommt es dann zur Neuinterpretation.
(*) Hängt auch zum Teil von den Betriebsbedingungen ab, ist als ungefähre Richtschnur aber brauchbar.

Wenn Du ein LOW oder ein HIGH suchst, wirst Du vergeblich in der Dokumentation des AVR dazu was finden. Die bitbasierten Namen HIGH/LOW sind Arduino-like. Zu finden in der Arduino.h

digitalRead() selbst ist eine Funktion die eben nur 0 oder 1 zurück gibt.

Nicht ganz.
Die Bedingung if (variable) ist immer erfüllt, solange variable != 0 ist.
Stell Dir das so vor, das in dem Moment wo kein Vergleich durchgeführt wird immer die Variable mit einem UND 1 gesetzt wird.

Aus (wert = 1) wird dann Ergebnis = (wert & 1); In Ergebnis steht dann 1.
Aus (wert = 0) wird dann Ergebnis = (wert & 1); In Ergebnis steht dann 0.
Ergebnis wahr oder falsch.
Um nun irgendwie aus der 0 wahr zu machen, wird die einfach negiert.
Ergebnis = ((~wert) & 1);

Und jetzt der spannende Teil:

Nein. Das ist kein Vergleich, sondern eine Zuweisung.
Die Variable Tasterstatus1 wird mit ihrem eigenen negierten Wert neu gefüllt.
Das könnte man auch lang schreiben:
temp = Tasterstatus;
Tasterstatus = ~temp;

An der Stelle hätte ich auch schreiben können Tasterstatus1=HIGH; weil ich weiß, das der Tasterstatus LOW sein muss um da hin zu kommen.
Aber so kleine Dinge zwischendurch lernen sich einfacher :wink:

Bei der LED ist es genauso.
Ich schalte den Pin nur um. Das geht natürlich auch in lang:

bool ledStatus = digitalRead(LED);
if (ledStatus == LOW)
{
  digitalWrite(LED, HIGH);
}
else
{
  digitalWrite(LED, LOW);
}

Das mit dem auslesen eines PIN als OUTPUT wurde schon erklärt...

Wichtig zum mitnehmen:
a != b ist ein Vergleich und fragt ab ob UNGLEICH
a = !b ist eine Zuweisung auf der linken Seite des negierten Wertes von rechts.

Vielen Dank für eure Antworten. Nach euren Erklärungen habe ich das System nun verstanden. Ansich ist es nicht so schwer nur ich merke das ich hin und wieder mit dem Gleichzeichen (Zuweisung) durcheinander komme im Sachen Verständnis. Mir fehlt halt noch diser Blick und damit Erkennung von den ganzen Zusammenhängen. Denke es wird aber mit der Zeit und Übung automatisch kommen.

Eine Sache im Program habe ich verändert. Von diesem Befehl:

Tasterstatus1 = !Tasterstatus1;

in diesen Befehl:

Tasterstatus1 = true;

Beide funktioniert, ist logischweise nichts anderer. Meine Frage ist nu ob ich das so machen kann oder ihr mir davon abratet da dies bei anderen Programbeispielen Probleme machen würde?

Falsch. Das ist viel anders.

  1. Tasterstatus 1 wird umgekehrt (1-->0 bzw. 0-->1)
  2. Tastersttus1 wird fest auf true gesetzt.

Gruß Tommy

Hallo,
es bleibt dann noch die Frage wie / wo Tasterstatus1 mal wieder "false" werden kann.
Heinz

Wenn Tasterstatus1 null bzw false bzw LOW ist dann machen beider Anweisungen das gleiche. Tasterstatus1 wird 1.
Wenn Tasterstatus1 bereits 1 / true/ HIGH ist dann machen die beiden Anweisungen was verschiedenes.

Grüße Uwe

Es dauert nicht mehr lang, dann ist dir klar, dass

if (Tasterstatus1 == true) { Tasterstatus1 = false; }
else  { Tasterstatus1 = true; }

eine fürchterlich umständliche (und fehleranfällige) Schreibweise ist.

Außer du willst noch mehr in den beiden Blöcken unterschiedlich machen.

In meiner Erklärung habe ich die Antwort auf die Frage bereits gegeben:

Wenn sich oben die Bedingung ändert und auf HIGH die Bedingung erfüllt ist, musst Du das zurücksetzen ebenfalls ändern.
Das entfällt beim togglen.

Stimmt, jetzt habe ich es auch gemerkt. Danke.