Forum: Mikrocontroller und Digitale Elektronik Arduino Struct für Generischen Schalter Debounce


von Karl-Heinz (Gast)


Lesenswert?

Hallo,

ich möchte gerne eine generische Methode schreiben, in der ich mehrere 
Schalter Entstelle. Hierzu nutze ich ein Struct indem ich meine Schalter 
definiere. Das ganze funktioniert auch teilweise. Ich kann mein Struct 
der Methode übergeben. Allerdings funktioniert es komischerweise nicht 
mit dem letzten "stateLast".

Nutze ich "buttons[2].stateLast" (so wie es aktuell in dem Code Steht) 
funktioniert es. Nutze ich "button.stateLast", funktioniert es leider 
nicht.

Weiss jemand woran es liegt? Alle anderen buttons[2] habe ich bereits 
durch die, für die Methode, Lokale "button" variable ersetzt.

Grüße

Karl
1
const unsigned long DEBOUNCE_DELAY = 50UL; //Button debounce timer
2
3
struct button_t
4
{
5
  const uint8_t   pin;
6
  uint8_t         state;
7
  uint8_t         stateLast;
8
  uint8_t         count;
9
  unsigned long   lastDebounceTime;
10
};
11
12
struct button_t     buttons[] =
13
{
14
  { BTN1_Pin, LOW, LOW, 0, 0UL }
15
  , { BTN2_Pin, LOW, LOW, 0, 0UL }
16
  , { 11, LOW, LOW, 0, 0UL }
17
};
18
19
// the loop function runs over and over again forever
20
void loop() {
21
  digitalWrite(LED_BUILTIN, Check(buttons[2]));
22
}
23
24
bool Check(struct button_t button)
25
{
26
  // read the state of the switch into a local variable:
27
  bool returnval = button.stateLast;
28
  int reading = !digitalRead(button.pin);
29
  if (reading != button.stateLast) {
30
    // reset the debouncing timer
31
    button.lastDebounceTime = millis();
32
  }
33
34
  if ((millis() - button.lastDebounceTime) > DEBOUNCE_DELAY) {
35
    // whatever the reading is at, it's been there for longer than the debounce
36
    // delay, so take it as the actual current state:
37
    // if the button state has changed:
38
    if (reading != button.state) {
39
      button.state = reading;
40
41
      // only toggle the LED if the new button state is HIGH
42
      if (button.state == LOW) {
43
        ledState = LOW;
44
        returnval = LOW;
45
46
      } else {
47
        returnval = HIGH;
48
      }
49
    }
50
  }
51
52
53
  // save the reading. Next time through the loop, it'll be the lastButtonState:
54
  buttons[2].stateLast = reading;
55
  return returnval;
56
}

von Klaus W. (mfgkw)


Lesenswert?

Wenn du Check(buttons[2]) aufrufst, dann wird die Funktion Check() 
aufgerufen und ihr wird eine Kopie von buttons[2] übergeben,
Wenn du in der Funktion dann zum Schluß button.stateLast=reading 
ausführst, wird .stateLast in der Kopie gesetzt, aber nicht im Original 
buttons[2].
Mit Ende von Check() verschwindet die Kopie und mit ihr der in 
.stateLast gesetzte Wert. Der im Original buttons[2].stateLast gesetzte 
Wert hat sich nicht geändert.
Beim nächsten Aufruf wird wieder eine Kopie angelegt, und die bekommt 
vom letzten button.stateLast nichts mit, das war ja nur in der ersten 
Kopie.

Was du wahrscheinlich willst, ist auf dem Original .stateLast zu setzen, 
damit es beim nächsten Aufruf von Check() noch da ist.
Check() soll also nicht eine Kopie bekommen, sondern auf dem Original 
arbeiten, damit sich .stateLast des Aufrufers ändert.

Damit eine Funktion etwas vom Aufrufer übergebenes ändern kann, gibt es 
in C Zeiger: man übergibt nicht eine Kopie, sondern einen Zeiger auf das 
Original.
Also statt:
1
  digitalWrite(LED_BUILTIN, Check(buttons[2]));
muß man schreiben:
1
  digitalWrite(LED_BUILTIN, &Check(buttons[2]));

Dazu muß die Funktion natürlich etwas anders deklariert sein; sie 
bekommt ja jetzt nicht mehr ein struct button_t übergeben, sondern die 
Adresse eines struct button_t (oder einen Zeiger auf ein struct 
button_t).
Statt
1
bool Check(struct button_t button)
2
{
3
...
heißt es jetzt:
1
bool Check(struct button_t *pButton)
2
{
3
...

Zudem muß man in der Funktion jetzt jeden Zugriff der Art:
1
    ...button.irgendwas...
ersetzen durch:
1
    ...(*pButton).irgendwas...
oder (vollkommen gleichwertig, nur schöner zu lesen):
1
    ...pButton->irgendwas...


Insbesondere heißt es dann zum Schluß:
1
...
2
  pButton->stateLast = reading;
3
  return returnval;
4
---
Damit wird dann wirklich .stateLast im buttons[2] des Aufrufers 
geändert, und wird beim nächsten Aufruf von Check() wieder wirksam.

HTH

von EAF (Gast)


Lesenswert?

Klaus W. schrieb:
> gibt es in C Zeiger

Das ist C++ also besser keine Zeiger, sondern Referenzen.
Wo immer es geht.

von Karl-Heinz (Gast)


Lesenswert?

Wow, vielen Dank! Das anschauliche Beispiel hat mir sehr geholfen!

Bei einem Punkt hast Du glaube ich einen vertipper drin, oder?

Anstatt
1
 digitalWrite(LED_BUILTIN, &Check(buttons[2]));

Müsste es
1
 digitalWrite(LED_BUILTIN, Check(&buttons[2]));

sein, oder? :)

Funktioniert jetzt Einwandfrei. Danke!

Grüße

Karl

von Peter D. (peda)


Lesenswert?

Ich würde die Funktion deutlich praxisgerechter implementieren. Ich 
würde daher das Entprellen in einem konstanten Intervall 
(Timerinterrupt) machen. Das hat den Vorteil, daß man Ereignisse 
auswerten kann, auch wenn die Taste schon wieder losgelassen wurde. Auch 
kann man dann lange Drücke oder Repeat auswerten.
Die Mainloop will in der Regel auch nicht den Zustand wissen, sondern 
die Flanke (Taste wurde gedrückt) behandeln.

von Klaus W. (mfgkw)


Lesenswert?

Karl-Heinz schrieb:
> Anstatt digitalWrite(LED_BUILTIN, &Check(buttons[2]));
>
> Müsste es digitalWrite(LED_BUILTIN, Check(&buttons[2]));
>
> sein, oder? :)

Ja, war bei mir falsch.

(Wollte natürlich nur testen, ob jemand mitdenkt ... :-)

EAF schrieb:
> Das ist C++ also besser keine Zeiger, sondern Referenzen.

Stimmt, in C++ nimmt man dann natürlich Referenzen.

Nur zum Verständnis: das ist aber dann nur eine andere und einfachere 
Schreibweise für die gezeigte Version mit den Zeigern. Intern wird für 
eine Referenzvariable ja auch nur ein Zeiger übergeben, und das * vor 
dem Zugriff automatisch generiert.

von Veit D. (devil-elec)


Lesenswert?

Hallo Karl-Heinz,

du musst dein struct zu einer Klasse (class) machen und alle benötigten 
Funktionen (Methoden) müssen in die Klasse. struct nimmt man mehr zum 
Daten zusammenfassen. Kannst aber auch erstmal bei struct bleiben, dann 
ist alles public und räumst hinterher auf. Ich finde es nur einfacher 
wenn man sich vorher Gedanken macht was public und was privat sein soll. 
Denn das aufräumen kann hinterher auch lästig werden. Am Ende kann man 
dann wirklich ein Array aus Taster Instanzen erstellen und mittels for 
Schleife iterieren.
Also, du schreibst dir eine Klasse mit Konstruktor und denkst dabei nur 
an einen einzigen Taster. Das ist der Bauplan für deine Taster Instanz. 
Das alles wäre C++ OOP.

von Klaus W. (mfgkw)


Lesenswert?

Veit D. schrieb:
> du musst dein struct zu einer Klasse (class) machen

Der Unterschied zwischen struct und class ist in C++ lediglich, ob die 
Elemente public oder private sind, bevor man es explizit hinschreibt.

Und es ändert nichts daran, daß man ein Objekt davon als Referenz oder 
per Zeiger übergeben muß, wenn man in einer Funktion etwas daran ändern 
will.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Klaus W. schrieb:
> Veit D. schrieb:
>> du musst dein struct zu einer Klasse (class) machen
>
> Der Unterschied zwischen struct und class ist in C++ lediglich, ob die
> Elemente public oder private sind, bevor man es explizit hinschreibt.

Das weiß ich. Nur gibt es eine ungeschriebene Regel wofür man struct und 
class verwendet. Das hatte ich erwähnt.

> Und es ändert nichts daran, daß man ein Objekt davon als Referenz oder
> per Zeiger übergeben muß, wenn man in einer Funktion etwas daran ändern
> will.

Das ist aber alles halbgares Zeug wenn die Methoden außerhalb der Klasse 
liegen usw. Bis zur richtigen gekapselten Lib ist es nicht weit. Wenn 
der TO Arduino hat kann er C++ programmieren.

von ... (Gast)


Lesenswert?

In C++ kann man genauso herumstuempern wie in C.
Wenn man von C keine Ahnung hat, wird das mit C++ nicht besser.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

vergiss nicht, jeder hat einmal angefangen, auch du. Wenn der TO etwas 
lernen möchte greift er das auf. Wenn er das Arduino Ökosystem einfach 
nur nutzen möchte nimmt er die Bounce2 Lib.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.