Forum: PC-Programmierung Funktion aufrufen, Wert nicht in anderen Funktionsaufruf verwenden


von Sim J. (unimetal)


Lesenswert?

Hallo,
in den letzten Tagen habe ich mir das Tutorial AVR-GCC etwas 
vorgenommen, um mit meinem STK500+Atmega8L ein weinig rumzuspielen.
Ich nutze AVR Studio 4.16 und als compiler die neuste WinAVR Version.

Ich habe mir in diesem Zusammenhang eine Entprellfunktion gebastelt. Die 
"millis" werden zuverlässig durch einen Timer erzeugt und sind notwendig 
für meine Entprellfunktion.

Der Funktionsaufruf erfolgt zum Beispiel mittels:
Debounce(PINC,PINC0)

Rückgabewert ist dann die Variable "Zustand", welche mir dann zum 
Beispiel über den Rückgabewert 1 oder 3 ein "entprellte" steigende oder 
sinkende Flanke ausgibt.

Die Funktion funktioniert zuverlässig, solange sie nur einmal in der 
Dauerschleife vorkommt. Nutze ich sie hingegen zum Beispiel für 
verschiedene Taster, dann beeinflussen die Variablen der beiden Aufrufe 
sich gegenseitig. Das sind meines Erachtens nach die Variablen:

static int Zustand;
static int Vorher;
static int UebergabeZustand;

static und das definieren von lokalen Variablen war nur ein Versuch, das 
Problem zu lösen und nicht zweckerfüllend.

Das Problem macht sich insoweit bemerkbar, dass mehrmalige Tastendrücke 
erkannt werden, obwohl diese gar nicht stattgefunden haben.

Hat jemand eine klügere Idee, die Funktion aufzurufen, Variablen zu 
definieren (ja ich weiß, alles mit int ist nichts ressourcensparend, 
sollte aber overflows vorerst vermeiden) oder erkennt, woran es hakt? 
Ich freue mich auf eure Antworten.

1
/*
2
FUSES:
3
Int. RC Osc. 1MHz => UNGENAUER WERT FÜR millis
4
*/
5
6
#include <avr/io.h>
7
#include <stdint.h>
8
#include <avr/interrupt.h>
9
10
//#include <stdbool.h>
11
volatile uint32_t millis = 0;
12
13
int Modus = 0;
14
15
//DEBOUNCE
16
uint32_t DebStamp = 0;
17
int DebTime = 50;
18
19
20
/*  DEBOUNCE FUNKTION                    Pinabfrage  Vorher  Verwendung
21
Zustand = 1 ->  Taster gedrückt, war nicht gedrückt      1      0    Tastendruck
22
Zustand = 2 ->  Taster gedrückt, war gedrückt        1      1    Während Taste gehalten wird
23
Zustand = 3 ->  Taster nicht gedrückt, war gedrückt      0      1    Tastenloslassen 
24
Zustand = 4 ->  Taster nicht gedrückt, war nicht gedrückt  0      0    Nachdem Taste losgelassen wurde
25
*/
26
int Debounce (int DebPort, int DebButton) {
27
  
28
  static int Zustand;
29
  static int Vorher;
30
  static int UebergabeZustand;
31
      
32
  //ZUSTAND 1
33
  if (((DebPort&(1<<DebButton)) == 0) && Vorher == 0 && UebergabeZustand == 0) {
34
    DebStamp = millis;
35
    UebergabeZustand = 1;
36
  }
37
38
  if (((millis - DebStamp) > DebTime) && ((DebPort&(1<<DebButton)) == 0) && UebergabeZustand == 1 && Vorher == 0) {
39
    Zustand = 1;
40
    Vorher = 1;
41
    UebergabeZustand = 0;
42
  }
43
44
  //ZUSTAND 2
45
  else if (((DebPort&(1<<DebButton)) == 0) && Vorher == 1 && UebergabeZustand == 0) {
46
    //Zustand = 2;
47
    UebergabeZustand = 0;
48
  }
49
50
  //ZUSTAND 3
51
  if ((DebPort&(1<<DebButton)) && Vorher == 1 && UebergabeZustand == 0 && Vorher == 1) {
52
    DebStamp = millis;
53
    UebergabeZustand = 3;
54
  }
55
56
  if (((millis - DebStamp) > DebTime) && (DebPort&(1<<DebButton)) && UebergabeZustand == 3 && Vorher == 1) {
57
    Zustand = 3;
58
    Vorher = 0;
59
    UebergabeZustand = 0;
60
  }
61
62
  //ZUSTAND 4
63
  else if ((DebPort&(1<<DebButton)) && Vorher == 0 && UebergabeZustand == 0) {
64
    //Zustand = 4;
65
    UebergabeZustand = 0;
66
  }
67
  
68
  return Zustand;
69
  }
70
71
72
int main(void) {
73
  
74
75
  //TIMER CONFIG
76
  OCR1A = 124;            //Vergleichswert = 0.1s*(1000000Hz/8)-1 = 125 -1 => ISR CA! alle 1ms
77
  TCCR1A |= 0x00;            //Keine notwendigen Bits (überflüssig, da std. 0)
78
  TCCR1B |= (1<<CS11)|(1<<WGM12);    //WHM12:CTC Modus mit OCR1A als oberen Wert | CS11:Prescaler = 8
79
  TIMSK |= (1<<OCIE1A);        //Isr soll auslösen, wenn Vergleichswert (OCR1A) erreicht ist
80
  
81
  sei();
82
  
83
  //PORTSETTINGS
84
  DDRD = 0xFF;            //Port D -> Ausgänge
85
  PORTD = 0xFF;            //Port D -> Alle Bits auf LOW
86
87
  DDRC = 0x00;            //Port C -> Eingänge
88
  PORTC = 0xFF;            //Port C -> Pull-Ups aktivieren
89
90
91
  while(1) {
92
        
93
    if (Debounce(PINC,PINC0) == 1 && Modus > 1)
94
    {
95
      Modus -= 1;
96
    }
97
98
    if (Debounce(PINC,PINC1) == 1 && Modus < 8)
99
    {
100
      Modus += 1;
101
    }
102
103
  }
104
105
return 0;
106
107
}
108
109
//ISR
110
ISR(TIMER1_COMPA_vect) {
111
millis += 1;
112
}

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Solange die Variablen in der Funktion als static deklariert sind, 
können sie gar nicht anders als sich gegenseitig zu beeinflussen.

Damit die Variablen sich nicht gegenseitig beeinflussen, müssen sie 
lokal (d.h. auf dem Stack) angelegt sein, und sie dürfen auf keinen Fall 
static sein.

Sollen die Variablen irgendwelche Werte über mehrere nacheinander 
ausgeführte Funktionsaufrufe behalten, dann musst Du für jede "Instanz" 
der Funktion (d.h. unterschiedliche Taster) eigene Variablen anlegen.

Das könntest Du tun, indem Du die Variablen in einer Struktur 
zusammenfasst und diese jeweils an der Stelle zur Verfügung stellst, wo 
Du die Funktion aufrufst; der Funktion übergibst Du dann einen Pointer 
auf die jeweilige zum Taster passende Struktur.

von Stefan F. (Gast)


Lesenswert?

Stichwort: Objektorientiert programmieren. Geht grundsätzlich auch in C 
indem man der objekt-spezifischen Funktion einen Zeiger auf die 
Datenstruktur des Objektes übergibt. C++ und Java machen das unter der 
Haube ebenso.

von Sim J. (unimetal)


Lesenswert?

Rufus Τ. F. schrieb:
> Solange die Variablen in der Funktion als static deklariert sind,
> können sie gar nicht anders als sich gegenseitig zu beeinflussen.
>
> Damit die Variablen sich nicht gegenseitig beeinflussen, müssen sie
> lokal (d.h. auf dem Stack) angelegt sein, und sie dürfen auf keinen Fall
> static sein.
->Macht Sinn. Denn die Werte werden ja dann für Taster 1 
zwischengespeichert, bis zum Aufruf der Funktion für Taster 2, die die 
alten Werte weiter bentzen will. Mit static läufts also nicht.

> Sollen die Variablen irgendwelche Werte über mehrere nacheinander
> ausgeführte Funktionsaufrufe behalten, dann musst Du für jede "Instanz"
> der Funktion (d.h. unterschiedliche Taster) eigene Variablen anlegen.
>
> Das könntest Du tun, indem Du die Variablen in einer Struktur
> zusammenfasst und diese jeweils an der Stelle zur Verfügung stellst, wo
> Du die Funktion aufrufst; der Funktion übergibst Du dann einen Pointer
> auf die jeweilige zum Taster passende Struktur.

Genau da liegt quasi meine Frage. Also müsste ich nach folgendem Schema 
arbeiten:

Erster Schleifendurchlauf:
->Funktionsaufruf Debounce(PINC,PINC0)
->Lege Variablen an:
static int VorherPINC0;
static int UebergabeZustandPINC0;
->Benutze diese Variablen, mit genau diesem Namen

->Funktionsaufruf Debounce(PINC,PINC1)
->Lege Variablen an:
static int VorherPINC1;
static int UebergabeZustandPINC1;
->Benutze diese Variablen, mit genau diesem Namen

Zweiter Schleifendurchlauf:
->Funktionsaufruf Debounce(PINC,PINC0)
->Lege KEINE neuen Variablen an und arbeite mit den alten Werten für:
VorherPINC1
UebergabeZustandPINC1

->Funktionsaufruf Debounce(PINC,PINC1)
->Lege KEINE neuen Variablen an und arbeite mit den alten Werten für:
VorherPINC1
UebergabeZustandPINC1

Frage wäre jetzt:
-Wie erzeuge ich die Variablen mit Name in Abhängigkeit von 
DebPort/DebButton
-Wie verhindere ich ein neu anlegen der Variablen (sollte über static 
gegeben sein, richtig?)

von M.K. B. (mkbit)


Lesenswert?

Besser wäre es, wenn du das ganze über eine externe Struktur an die 
Methode weitergibst. Wie auch schon von rufus und stefanus 
vorgeschlagen.
1
struct DebounceZustand
2
{
3
    int Zustand;
4
    int Vorher;
5
    int UebergabeZustand;
6
};
7
8
int Debounce (int DebPort, int DebButton, DebounceZustand* pZustand);
9
10
// Für jeden Port
11
DebounceZustand ZustandPort1;
12
13
while(1) {
14
    Debounce(PINC,PINC1, &ZustandPort1);
15
}

Du musst die Variablen des Zustandes außerdem initialisieren. In deinem 
Fall sind diese 0, weil sie static Variablen sind (C Standard). Sobald 
diese auf dem Stack liegen ist das nicht mehr der Fall.

Ich würde außerdem für die Zustände ein enum verwenden. Im Prinzip ist 
es das gleiche, wie deine Zahlen, nur dass die Zahlen dann sprechende 
Namen haben können und der Code besser verständlich ist.

von Stefan F. (Gast)


Lesenswert?

Die richtigen Stichwörter habe ich Dir gegeben. Aber anscheinend hast du 
nicht nach Anleitung gesucht, sondern lieber hier nochmal (dumm) 
gefragt.

M.K. B hat Dir ein konkreten Beispiel gegeben - sehr schön, aber lesen 
solltest du trotzdem:

https://www.elektronikpraxis.vogel.de/objektorientierte-programmierung-mit-c-a-252633/index2.html
https://panthema.net/2005/0614-RLP-Vortrag-OOC/OOC-Folien.pdf

Lies das, dann kann du wesentlich schlauere Fragen zu Details stellen.

von Peter D. (peda)


Lesenswert?

Besonders speichersparend ist das vertical Counter Prinzip, d.h. jede 
Taste entspricht einem Bit in der Variable (uint8_t für bis zu 8 Tasten, 
uint16_t bis 16 Tasten usw.).
Die geringsten Seiteneffekte hat man, wenn man das Entprellen und Flanke 
erkennen in den Timerinterrupt auslagert und das Main muß sich nur noch 
die Tastenereignisse abholen. Dann kann das Main ruhig mal etwas länger 
busy sein, ohne das Tastendrücke verloren gehen.

von Peter D. (peda)


Lesenswert?

M.K. B. schrieb:
> struct DebounceZustand
> {
>     int Zustand;
>     int Vorher;
>     int UebergabeZustand;
> };

Ist nicht so der Bringer, wenn man z.B. auf nem ATtiny13 mit 64 Byte 
SRAM je Taste schon 6 Byte verbraucht.

von Pandur S. (jetztnicht)


Lesenswert?

Es ist viel einfacher, jeder einzelne Taster hat einen globalen Zustand, 
da ist nicht mit "uebersprechen". Ferner verwendet man keine Debounce 
procedure, sondern laesst den KeyProcess bei jedem Timer durch, und 
setzt dort ein flag wenn sich etwas veraendert hat. Im main schaut man 
obs Veraenderungen gab.

: Bearbeitet durch User
von Sim J. (unimetal)


Lesenswert?

Stefan U. schrieb:
> Die richtigen Stichwörter habe ich Dir gegeben. Aber anscheinend hast du
> nicht nach Anleitung gesucht, sondern lieber hier nochmal (dumm)
> gefragt.

Hi Stefan,
die Antwort habe ich angefangen zu verfassen, bevor du geantwortet hast. 
Tatsächlich habe ich wohl 25min an meiner Antwort geschrieben, da ich 
während des antwortens die Aussagen von rufus versucht habe 
nachzuvollziehen. Als ich auf die Vorschau gegangen bin, ist mir deine 
Antwort ins Auge gestochen, jedoch bin ich nicht drauf eingegangen, da 
bei eisiger Kälte ein Kumpel vor der Türe auf mich gewartet hat :D
Normalerweise ist es nicht meine Art, eine Antwort einfach so zu 
überlesen.

Ich werde jetzt die Antworten mal durchgehen, entsprechend 
nachvollziehen und  schauen, wie ich vorgehe.

Peter D. schrieb:
> Ist nicht so der Bringer, wenn man z.B. auf nem ATtiny13 mit 64 Byte
> SRAM je Taste schon 6 Byte verbraucht.

Das war doch nur ein Beispiel. Im Eingangspost wurde doch von mir extra 
erwähnt, dass das so erstmal nur zum testen ist.

Ziel ist es auf jeden Fall, eine auf andere Programme gut adaptierbare 
Lösung zu erstellen. Die Lösung mit den millis finde ich recht 
ansprechend, da sie vielseitig, auch für andere Teile des Programmes, 
verwendet werden kann.

Beste Grüße und danke für alle, die sich die Mühe machen, hier zu 
antworten.

von Peter D. (peda)


Lesenswert?

Sim J. schrieb:
> Frage wäre jetzt:
> -Wie erzeuge ich die Variablen mit Name in Abhängigkeit von
> DebPort/DebButton

Das ist alles andere als trivial.

M.K. B. schrieb:
> int Debounce (int DebPort, int DebButton, DebounceZustand* pZustand);

Wäre eine Möglichkeit, bloß der Aufrufoverhead steigt damit noch weiter 
an (3 Argumente: Copy&Paste Fehlergefahr).
Ein andere wäre, den ganzen Port zu entprellen und die gewünschte Taste 
per Bitmaske zu übergeben.

Der Hauptnachteil bleibt allerdings bestehen, daß Dir Tastendrücke 
verloren gehen, sobald das Main beschäftigt ist. Daher trennt man 
üblicher Weise Entprellen (Timerinterrupt) und Aktion (Main) 
voneinander.

von Sim J. (unimetal)


Lesenswert?

Peter D. schrieb:
> Besonders speichersparend ist das vertical Counter Prinzip, d.h.
> jede
> Taste entspricht einem Bit in der Variable (uint8_t für bis zu 8 Tasten,
> uint16_t bis 16 Tasten usw.).
> Die geringsten Seiteneffekte hat man, wenn man das Entprellen und Flanke
> erkennen in den Timerinterrupt auslagert und das Main muß sich nur noch
> die Tastenereignisse abholen. Dann kann das Main ruhig mal etwas länger
> busy sein, ohne das Tastendrücke verloren gehen.

Das mit dem Vertical Counter Prinzip klingt auf jeden Fall interessant. 
Das werde ich mir bei Gelegenheit mal näher ansehen.

Sabberalot W. schrieb:
> Es ist viel einfacher, jeder einzelne Taster hat einen globalen
> Zustand,
> da ist nicht mit "uebersprechen". Ferner verwendet man keine Debounce
> procedure, sondern laesst den KeyProcess bei jedem Timer durch, und
> setzt dort ein flag wenn sich etwas veraendert hat. Im main schaut man
> obs Veraenderungen gab.

Quasi Taste beispielsweise alle 50ms abfragen, Zustand aller Taster in 
Variable auslagern (zB nach Vertical Counter Prinzip in einem Byte) und 
dann den Input in der Main entsprechend verarbeiten?

Peter D. schrieb:
> M.K. B. schrieb:
>> int Debounce (int DebPort, int DebButton, DebounceZustand* pZustand);
>
> Wäre eine Möglichkeit, bloß der Aufrufoverhead steigt damit noch weiter
> an (3 Argumente: Copy&Paste Fehlergefahr).
> Ein andere wäre, den ganzen Port zu entprellen und die gewünschte Taste
> per Bitmaske zu übergeben.
>
> Der Hauptnachteil bleibt allerdings bestehen, daß Dir Tastendrücke
> verloren gehen, sobald das Main beschäftigt ist. Daher trennt man
> üblicher Weise Entprellen (Timerinterrupt) und Aktion (Main)
> voneinander.

Copy&Paste Fehlergefahr...den muss ich mir merken :D
Hab mir jetzt ein bisschen Input verschafft und denke, dass ich die 
Sache jetzt verstanden habe. Mir recht das erstmal so. Funktioniert 
nämlich super und war ja das, was ich wollte. Obs im konkreten Fall des 
Entprellens so sinnvoll ist, ist die andere Frage. Wenn der Speicher mal 
knapp wird, kann ich immernoch "tunen".

Hier nochmal der aktuelle Code:
1
/*  DEBOUNCE FUNKTION                    Pinabfrage  Vorher  Verwendung
2
Zustand = 1 ->  Taster gedrückt, war nicht gedrückt      1      0    Tastendruck
3
Zustand = 2 ->  Taster gedrückt, war gedrückt        1      1    Während Taste gehalten wird
4
Zustand = 3 ->  Taster nicht gedrückt, war gedrückt      0      1    Tastenloslassen 
5
Zustand = 4 ->  Taster nicht gedrückt, war nicht gedrückt  0      0    Nachdem Taste losgelassen wurde
6
*/
7
8
//DEBOUNCE
9
uint32_t DebStamp = 0;
10
int DebTime = 50;
11
12
struct DebounceStruct{            //Erstelle eigenen Datentyp, welcher Zustand, Vorher und UebergabeZustand beinhaltet
13
  uint8_t Zustand;
14
  bool Vorher;
15
  uint8_t UebergabeZustand;
16
};
17
18
19
struct DebounceStruct Taster0;
20
struct DebounceStruct Taster1;
21
struct DebounceStruct Taster2;
22
struct DebounceStruct Taster3;
23
struct DebounceStruct Taster4;
24
struct DebounceStruct Taster5;
25
struct DebounceStruct Taster6;
26
struct DebounceStruct Taster7;
27
28
29
int Debounce (int DebPort, int DebButton, struct DebounceStruct *pZustand) { 
30
  
31
  //ZUSTAND 1
32
  if (((DebPort&(1<<DebButton)) == 0) && (*pZustand).Vorher == 0 && (*pZustand).UebergabeZustand == 0) {
33
    DebStamp = millis;
34
    (*pZustand).UebergabeZustand = 1;
35
  }
36
37
  if (((millis - DebStamp) > DebTime) && ((DebPort&(1<<DebButton)) == 0) && (*pZustand).UebergabeZustand == 1 && (*pZustand).Vorher == 0) {
38
    (*pZustand).Zustand = 1;
39
    (*pZustand).Vorher = 1;
40
    (*pZustand).UebergabeZustand = 0;
41
  }
42
43
  //ZUSTAND 2
44
  else if (((DebPort&(1<<DebButton)) == 0) && (*pZustand).Vorher == 1 && (*pZustand).UebergabeZustand == 0) {
45
    (*pZustand).Zustand = 2;
46
    (*pZustand).UebergabeZustand = 0;
47
  }
48
49
  //ZUSTAND 3
50
  if ((DebPort&(1<<DebButton)) && (*pZustand).Vorher == 1 && (*pZustand).UebergabeZustand == 0 && (*pZustand).Vorher == 1) {
51
    DebStamp = millis;
52
    (*pZustand).UebergabeZustand = 3;
53
  }
54
55
  if (((millis - DebStamp) > DebTime) && (DebPort&(1<<DebButton)) && (*pZustand).UebergabeZustand == 3 && (*pZustand).Vorher == 1) {
56
    (*pZustand).Zustand = 3;
57
    (*pZustand).Vorher = 0;
58
    (*pZustand).UebergabeZustand = 0;
59
  }
60
61
  //ZUSTAND 4
62
  else if ((DebPort&(1<<DebButton)) && (*pZustand).Vorher == 0 && (*pZustand).UebergabeZustand == 0) {
63
    (*pZustand).Zustand = 4;
64
    (*pZustand).UebergabeZustand = 0;
65
  }
66
67
  return (*pZustand).Zustand;
68
69
  }

So kann ich den Zustand in der Main bei Bedarf bequem abrufen über:
xxx = Debounce(PINC,PINC0, &Taster0);

Beste Grüße und vielen Dank an alle

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Sim J. schrieb:

[...]
> So kann ich den Zustand in der Main bei Bedarf bequem abrufen über:
> xxx = Debounce(PINC,PINC0, &Taster0);

OMG

Wenn das kein lupenreines Argument wider die C-Seuche ist, was denn 
dann?

Das geht in Assembler nicht nur mit weniger Rechenzeit (das wäre ja 
sowieso erwartbar), nein es geht auch noch mit deutlich weniger Zeilen 
Code. Wo genau ist also hier der Vorteil des Einsatzes einer 
(sogenannten) Hochsprache?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Geh weg, Du nervst.

von Stefan F. (Gast)


Lesenswert?

> Wenn das kein lupenreines Argument wider die C-Seuche ist,
> was denn dann?

Es ist niemals eine gute Idee, eine schlechte Anwendung von einer Sache 
als Gegenargument für die Sache zu verwenden. Falls du da anderer 
Meinung bis, solltest du alle deine Schraubendreher wegwerfen, weil man 
damit ziemlich schlechte Sachen anstellen kann.

> Wo genau ist also hier der Vorteil des Einsatzes einer
> (sogenannten) Hochsprache?

Das weißt du längst, stell dich nicht so blöd, nur um hier zu 
provozieren!

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.