Go Down

Topic: [Projekt] INTERVAL Ersatzstoff (Read 3252 times) previous topic - next topic

combie

Dec 10, 2017, 01:01 pm Last Edit: Dec 12, 2017, 01:00 pm by combie
Hier möchte ich euch meinen INTERVAL Ersatzstoff vorstellen.

Wer gar nicht weiß, worum es sich dreht, sollte dieses lesen:
Blink Without Delay
Der Wachmann

Hält man sich an die dort vorgeschlagenen Wege, was auch gut und richtig ist, dann wird der Quellcode mit solchen Statements geflutet:
Code: [Select]

if(millis() - gemerkteZeit >= wunschLaufzeit)
{
  // tuwas
  gemerkteZeit = millis();
}

Die Wurst ist total korrekt.
Aber dennoch unangenehm, da mit viel Schreibarbeit verbunden.
Es schleichen sich schnell Copy+Paste Fehler ein.
Der Quellcode des Projektes wird recht unübersichtlich. Um so schlimmer, je öfter das Konstrukt darin vorkommt.


Hier die von mir erstellten Abstraktionen des Problems:
Multitasking Macros
Intervall Macro

An dieser Stelle ein herzliches Danke, an die Spender der (in den Threads versteckten) Anregungen.
Im folgenden habe ich versucht sie weitestgehend aufzunehmen.



Zum "WARUM?" bei mir ein Ersatzstoff, für INTERVAL und die TaskMakros, her muss:

Eigentlich funktionieren diese Dinge prächtig!
Sind aber gewissen Einschränkungen unterworfen, was hauptsächlich an der Verwendung der statischen Daten liegt. Diese verhindern eine Mehrfachverwendung der erstellten Funktionen. Ich nenne solche Funktionen "Wegwerf Funktionen". Denn sie sind nur einmal pro Projekt nutzbar. Braucht man z.B. drei unabhängige Wechselblinker, dann muss man die Funktion kopieren, so dass man, im schlimmsten Fall, 3 vollständig identische Kopien in einem Programm hat.
Ebenso verhält es sich wenn man diese Makros in Klassen einbaut, dann ist nur eine Instanz nutzbar. Mehrere Instanzen führen zu Fehlverhalten. Eine "Wegwerf Klasse".

Was im prozeduralen Umfeld gerade noch erträglich erscheint, macht im OOP Umfeld die wichtigsten OO Möglichkeiten kaputt.
Dabei ist doch gerade die Wiederverwendbarkeit eins der vordringlichsten Ziele der OOP.

Ist klar geworden, wo der Hase im Pfeffer liegt?




Hier jetzt der neue Ansatz, um das BlinkWithoutDelay Problem zu erschlagen:
BlinkWithSimpleTimer.ino
Code: [Select]
#include "CombieTimer.h"

Combie::SimpleTimer timer; // timer Instanz anlegen

void setup()
{
  pinMode(LED_BUILTIN,OUTPUT);
    // timer.start();
}

void loop()
{
  if(timer(1000)) // wenn abgelaufen
  {
    digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
    timer.start();
  }


Ich nutze NameSpaces, da es schon eine SimpleTimer Lib gibt, und ich damit nicht kollidieren möchte. Wenn man sich den Code anschaut, dann ist mein SimpleTimer deutlich simpler, als der fremde SimpleTimer.

Daraus leite ich, für mich, das moralische Recht ab meinen SimpleTimer auch so zu nennen.
:smiley-twist:  :smiley-twist:


Erklärung:
Der SimpleTimer startet immer im abgelaufenen Zustand. Er wird also bei der ersten Nutzung true liefen. Will man das nicht, sollte man in setup() ein timer.start() notieren, so wie es im Beispiel vorbereitet ist.



Vergleichbares hier nochmal in einer abgewandelten Version:
BlinkWithPulsator.ino
Code: [Select]

#include "CombieTimer.h"

Combie::Pulsator puls(1000); // liefert alle 1000 ms einmal true sonst false

void setup()
{
  pinMode(LED_BUILTIN,OUTPUT);
  puls.start(); // dank des start(), beginnt puls mit Pause
}

void loop()
{
  if(puls) digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));



Eine Entprelleinrichtung, basierend auf dem SimpleTimer:
TasterEntprellen.ino
Code: [Select]
#include "CombieTimer.h"
using Combie::EntprellTimer;

const byte taster =  2; // taster gegen GND schaltend

EntprellTimer entprellen(200);   // in der Praxis wird man wohl eher 20ms verwenden

void setup()
{
 pinMode(LED_BUILTIN,OUTPUT);   
 pinMode(taster,INPUT_PULLUP);
}

void loop()
{
  // die LED zeigt das, vom prellen bereinigte, Signal
  digitalWrite(LED_BUILTIN,entprellen(!digitalRead(taster)));  // invers, wg. pullup
}



Und zum Abschluss noch eine Aufgabe um ein Signal zu formen:
Schaltsequenz.ino
Code: [Select]
/**
 *    Ablaufdiagramm - Zeitdiagramm
 *   
 *        S1    _----------_____  Schalterstellung
 *        OUT1  _-------------__  Verzoegertes abschalten
 *        OUT2  ____-------_____  Verzoegertes einschalten
 *
*/

#include "CombieTimer.h"
using namespace Combie;

const byte S1   = 2; // Schalter Pin
const byte OUT1 = 3; // Ausgang
const byte OUT2 = 4; // Ausgang

EntprellTimer    entprell(20); // Schalter entprellen
RisingEdgeTimer  ton(500);     // steigende Flanke wird verzoegert
FallingEdgeTimer toff(500);    // abfallende Flanke wird verzoegert
 
void setup(void)
{
  pinMode(S1,INPUT_PULLUP);
  pinMode(OUT1,OUTPUT);
  pinMode(OUT2,OUTPUT);
}

void loop(void)
{
  bool schalter = entprell(!digitalRead(S1)); // invers wg. pullup
  digitalWrite(OUT1, toff(schalter));
  digitalWrite(OUT2, ton(schalter));
}



-------------------

Zu guter Letzt noch die CombieTimer.h in welcher die ganze Magie steckt:
Code: [Select]
#pragma once

namespace Combie
{
   
   
    class SimpleTimer
    {
      private:
      unsigned long timeStamp = 0;
      bool abgelaufen = true; // default Status: timer abgelaufen
   
      public:
      void start()
      {
        timeStamp   = millis();
        abgelaufen  = false;
      }
   
      bool operator()(const unsigned long ablaufZeit)
      {
        if(!abgelaufen) abgelaufen = millis() - timeStamp >= ablaufZeit;
        return abgelaufen;
      }
    };
   
   
    class Pulsator   // liefert alle X ms einen HIGH Impuls
    {
      protected:
      SimpleTimer timer;
      unsigned long interval;
     
      public:
      Pulsator(unsigned long interval):interval(interval){}
     
      void setInterval(const unsigned long _interval)
      {
         interval =  _interval;
      }

     
      void start()
      {
        timer.start();
      }
     
      operator bool()
      {
          bool result = false;
          if(timer(interval))
          {
            result = true;
            timer.start();
          }
          return result;
      }
    };
   
    class EdgeTimer     // Die abstakte Mutter der Flankenverzoegerer
    {
      protected:
      SimpleTimer timer;
      unsigned long laufzeit = 0;
   
      public:
      explicit EdgeTimer(const unsigned long laufzeit):laufzeit(laufzeit){}
     
      virtual bool operator()(const bool trigger) = 0;
     
      void setLaufzeit(const unsigned long _laufzeit)
      {
         laufzeit =  _laufzeit;
      }
    };
   
    class FallingEdgeTimer: public EdgeTimer // abfallende Flanke wird verzoegert
    {
      public:
      using EdgeTimer::EdgeTimer;
     
      virtual bool operator()(const bool trigger)
      {
        if(trigger) timer.start();
        return !timer(laufzeit);
      }
    };
   
    class RisingEdgeTimer: public EdgeTimer // steigende Flanke wird verzoegert
    {
      public:
      using EdgeTimer::EdgeTimer;
     
      virtual bool operator()(const bool trigger)
      {
        if(!trigger) timer.start();
        return timer(laufzeit);
      }
    };
   
    class EntprellTimer
    {
      private:
      SimpleTimer timer;
      bool merker = false;  //  zustand
      const unsigned long entprellzeit;
     
      public:
      explicit EntprellTimer(const unsigned long entprellzeit) : entprellzeit(entprellzeit)  {}
     
      bool operator()(const bool trigger)
      {
         if(merker == trigger)   timer.start();
         if(timer(entprellzeit)) merker = !merker;
         return merker;
      }
    };
   
}



Die CombieTimer.h kann man einfach mit ins Projektverzeichnis werfen und wird dann von Arduino in einem Tab angezeigt.




Vorschläge?
Ansichten?
Kritik?
Würde mich freuen!
Es ist offensichtlich, dass uns die Umstände alleine nicht glücklich oder unglücklich machen.
Es ist die Art unserer Reaktion darauf, die unsere Gefühle bestimmt.

Serenifly

Sieht gut aus :)


Der bool Operator in EdgeTimer sollte abstrakt sein. Im Kommentator schreibst du auch dass dass so sein soll:
Code: [Select]

virtual bool operator()(const bool trigger) = 0;

In C++ nennt sich das auch "pure virtual function"

combie

#2
Dec 10, 2017, 01:52 pm Last Edit: Dec 10, 2017, 02:00 pm by combie
Danke für die Blumen!


Siehste, das "=0" ist mir doch glatt durchgerutscht!
Beabsichtigt war es, da hast du vollkommen recht.
(done+danke)


Es ist offensichtlich, dass uns die Umstände alleine nicht glücklich oder unglücklich machen.
Es ist die Art unserer Reaktion darauf, die unsere Gefühle bestimmt.

Tommy56

Das sieht sehr gut aus. Besonders interessant ist, wie vielfältig die Anwendung einer Grundfunktion durch wenig Steuercode gestaltet werden kann.

Gruß Tommy
"Wer den schnellen Erfolg sucht, sollte nicht programmieren, sondern Holz hacken." (Quelle unbekannt)

ElEspanol

#4
Dec 10, 2017, 03:03 pm Last Edit: Dec 10, 2017, 03:03 pm by ElEspanol
Hab auch mal ein bisschen mit dem Timer rumgespielt. Ausser, dass man eine Einschaltschaltverzögerung bauen kann, sehe ich noch keine Vorteile gg. INTERVAL.h
Nachteilig ist, dass ja der Befehl timer.start(); noch mit rein muss.
Oder liegen die Vorteile unter der Haube?

Bin INTERVAL.h Fan der ersten Stunde

combie

#5
Dec 10, 2017, 03:16 pm Last Edit: Dec 10, 2017, 03:31 pm by combie
Quote
sehe ich noch keine Vorteile gg. INTERVAL.h
Wenn INTERVAL für dich funktioniert, dann ist es genau das richtige für dich.

Ich bin auch weiterhin ein wenig Stolz auf den INTERVAL Ansatz!
Verwende ihn gerne.
Und froh, dass er dir gefällt!


Die Vorteile des SimpleTimer liegen eher im strukturellen Bereich.
Ins Besondere, wenn es Richtung Wiederverwendung und Vererbung geht.
Das sind die Bereiche, wo einem INTERVAL dann im Weg rum steht.
Es ist offensichtlich, dass uns die Umstände alleine nicht glücklich oder unglücklich machen.
Es ist die Art unserer Reaktion darauf, die unsere Gefühle bestimmt.

uxomm

Bin auch INTERVAL.h Fan und verwende das häufig. Besten Dank für den Code!
Bin aber auch schon sehr neugierig was hier entwickelt wird! :)
Always decouple electronic circuitry.

combie

@Tommy
Auch an dich, meinen Dank, für die Anerkennung.


Es war ein recht weiter Weg bis dahin.
Eigentlich bin ich erst mit C++ angefangen, als ich hier eingeschlagen bin.
C und andere Sprachen waren mir da schon bekannt.

Da stecken also so ca 3 Jahre hobbymäßige Beschäftigung drin.
Sicherlich 1000 Versuche, irgendwelche Timer zu basteln, von denen ihr hier im Forum nur eine kleine Auswahl gesehen habt.
Das ist auch gut so, denn die meisten waren Irrwege.
Es ist offensichtlich, dass uns die Umstände alleine nicht glücklich oder unglücklich machen.
Es ist die Art unserer Reaktion darauf, die unsere Gefühle bestimmt.

ElEspanol

#8
Dec 10, 2017, 03:46 pm Last Edit: Dec 10, 2017, 03:50 pm by ElEspanol
Ich arbeite gerne mit wechselnden Intervallzeiten bei dem jeweiligen Timern. Normalblink, Schnellblink, asymetrisch, Blitz, etc.

Etwa. z.B. so
Code: [Select]
#include <CombieTimer.h>
Combie::SimpleTimer timer; // timer Instanz anlegen

uint32_t interval = 1000;

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  if (timer(interval)) // wenn abgelaufen
  {
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    interval = digitalRead(LED_BUILTIN) ? 200 : 1000;
    timer.start();
  }
}

Leider muss man dafür immer eine globale Variable (uint32_t interval=1000; in diesem Fall) setzen. Kannst du nix einbauen, was direkt gesetzt (also an beliebiger Stelle im Sketch) werden kann, z.B.:
timer.interval(500)
um dann sowas zu machen:
timer.interval(digitalRead(LED_BUILTIN) ? 200 : 1000);
und dies dann in der
 if(timer(interval)) benutzt wird?

Serenifly

#9
Dec 10, 2017, 03:49 pm Last Edit: Dec 10, 2017, 03:54 pm by Serenifly
In EntprellTimer solltest du noch merker initialisieren

Und geht das nicht einfacher?
Code: [Select]

if(!(merker^trigger))




@ElEspanol
Für symmetrisches Blinken muss man nur Pulsator um eine Setter-Methode erweitern

combie

#10
Dec 10, 2017, 04:13 pm Last Edit: Dec 10, 2017, 04:21 pm by combie
Quote
timer.interval(digitalRead(LED_BUILTIN) ? 200 : 1000);
So:??

Code: [Select]
#include "CombieTimer.h"

Combie::Pulsator puls(1000);

void setup()
{
  pinMode(LED_BUILTIN,OUTPUT);
}

void loop()
{
  if(puls)
  {
    digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
    puls.setInterval(digitalRead(LED_BUILTIN) ? 200 : 1000);
  } 
}


Die CombieTimer.h im Startbeitrag angepasst.
Es ist offensichtlich, dass uns die Umstände alleine nicht glücklich oder unglücklich machen.
Es ist die Art unserer Reaktion darauf, die unsere Gefühle bestimmt.

combie

#11
Dec 10, 2017, 04:20 pm Last Edit: Dec 10, 2017, 05:07 pm by combie
In EntprellTimer solltest du noch merker initialisieren
Done!

Wobei mein C++ Buch da recht klar ist:
Statische und globale Variabeln, und Objekteigenschaften werden automatisch initialisiert.
Mit einem int 0 entsprechendem Wert des passenden Typs.
Oder muss ich das Kapitel nochmal lesen?

Und geht das nicht einfacher?
Code: [Select]

if(!(merker^trigger))

Tja....
Nachdem ich ein Wenig an dem Problem herumgestolpert bin, habe ich mir schlussendlich eine Wahrheitstabelle aufgemalt.
Und der Ausdruck ist das direkte Ergebnis dieser Bemühungen.

Falls dir was schöneres einfällt, men los, habe ein offenes Ohr dafür.
Code: [Select]
if(merker == trigger)
Done!



@ElEspanol
Für symmetrisches Blinken muss man nur Pulsator um eine Setter-Methode erweitern
Done.
Es ist offensichtlich, dass uns die Umstände alleine nicht glücklich oder unglücklich machen.
Es ist die Art unserer Reaktion darauf, die unsere Gefühle bestimmt.

Serenifly

#12
Dec 10, 2017, 04:57 pm Last Edit: Dec 10, 2017, 05:00 pm by Serenifly
Das ist etwas komplizierter. Wenn eine Variable nicht in der Initialisierungs-Liste greift die Default Initialisierung. Und die ist für die eingebauten Standard-Datentypen undefiniert.
Unter bestimmten Umständen (und C++11 spielt da auch eine Rolle!) kann aber eine Initialisierung auf Null erfolgen. Das hängt davon ab wie und wo man das Objekt erstellt. Ob das Objekt selbst global oder lokal ist kann da glaube ich auch eine Rolle spielen.
Daher sollte man im Zweifelsfall alles initialisieren. Siehe auch den Abschnitt "Notes" hier:
http://en.cppreference.com/w/cpp/language/value_initialization

Das ist auch ein Punkt wo Debugger verwirren können. Die sind teilweise so nett und führen da eine Initialisierung durch.



Bei dem Ausdruck geht es doch nur darum abzufragen ob die zwei Ausdrücke gleich oder ungleich sind, oder?

combie

#13
Dec 10, 2017, 05:10 pm Last Edit: Dec 10, 2017, 05:13 pm by combie
Quote
Bei dem Ausdruck geht es doch nur darum abzufragen ob die zwei Ausdrücke gleich oder ungleich sind, oder?
Das ist einer dieser Groschen, welcher manchmal etwas braucht....
Schon geändert.


Ja, das Initialisierungskapitel tue ich mir nochmal an!
Da stehen aber auch viele Sachen drin, in so einem C++ Buch ;-)

Es ist offensichtlich, dass uns die Umstände alleine nicht glücklich oder unglücklich machen.
Es ist die Art unserer Reaktion darauf, die unsere Gefühle bestimmt.

Serenifly

Ich gebe auch nicht an auf Anhieb zu wissen wann da genau was statt findet. Am einfachsten ist es anzunehmen dass die Variable nicht initialisiert sein kann :)

Go Up