Fortlaufender Zähler in C

OP #2297861
Lesenswert?

Hallo zusammen,
Ich bin gerade dabei ein Programm zu entwickeln, das bei Knopfdruck 
einen bestimmten Wert herunterzählt, währenddessen einen Ausgang 
ansteuert und bei abgelaufener Zeit diesen Ausgang wieder abschaltet.
Diese Funktion ist im Anhang bereits realisiert. Jedoch möchte ich, dass 
bei erneutem Drücken des Tasters während der Zähler herunterzählt die 
ursprüngliche Zahl im Zähler zum aktuellen Zählerstand addiert wird.

Beispiel:

Zählt von 10 nach 0, Zählerstand ist bei 6, nach erneutem Drücken des 
Tasters ist der Zählerstand bei 6 + 10, also bei 16, jetzt zählt der 
Zähler von 16 naach 0, Zählerstand is inzwischen bei angenommen 3, nach 
erneutem Drücken des Tasters is der Zählerstand nun bei 3 + 10, also bei 
13 usw...

Vielleicht kann mir jemand helfen
Danke schon mal für die Bemühungen
Basti
Angehängte Dateien:
Gast #2297884
Lesenswert?

Prinzipiell würde ich das so machen:

Globale Variable Counter

Timer Interrupt (jede 1 s)
{
  Wenn Counter > 0
     Counter --;
}

Main
{
  Wenn Counter == 0
     PORTB = Zustand1
  Sonst
     PORTB = Zustand2

  Wenn (Taste kurz gedrückt) // Entprellung nicht vergessen
    Counter += 10
}


Zum Entprellen des Tasters kann ich die Codes von Peter Dannegger 
empfehlen:
http://www.mikrocontroller.net/articles/Entprellung#Debounce-Makro_von_Peter_Dannegger
Gast #2297949
Lesenswert?

Gerne - ich geben gern zu, dass es am Anfang etwas unübersichtlich ist. 
Ich würde zunächst das Programm ohne Entprellen so wie dein obiges 
Beispiel bauen. Also durch drücken auf die Taste, wird Counter auf 10 
gesetzt (nicht addiert). Dann spielt nämlich das Prellen des Tasters 
keine maßgebliche Rolle und das Programm wird erstmal einfacher. 
Erweitern können wir es dann anschließend.
Gast #2297968
Lesenswert?

@Düsendieb: Entweder ich habe dich jetzt nicht verstanden, oder das 
funktioniert so nicht.

Wenn die Taste einmal gedrückt wird, wird Counter auf 10 gesetzt (da 
Counter = 0 und Taste gedrückt). Jetzt zählt er auf 4 runter und ich 
drücke die taste erneut. Erwarten würde ich, dass counter jetzt auf 14 
gesetzt wird. Da counter aber ungleich 0 ist deine Bedingung unwahr und 
es wird nichts addiert.
OP #2299160
Lesenswert?

servus Michael,
Ich komm mit dem counter, der jede Sekunde durch den Interrupt 
unterbrochen werden soll, gar nicht zurecht, außerdem kann ich nirgendwo 
herauslesen wie der interrupt ordendlich programmiert wird. Kannst Du 
mir sagen wie ich das schreiben soll?

Also sorry wenn ich hier auf Anfängerkram rumhacke, aber hab halt 
ziemlich wenig Ahnung...

gruß
Gast #2299184
Lesenswert?

Mit Timerinterrupt ist es sicher eleganter aber für dieses Problem geht 
es auch noch genausogut ohne Interrupt:
1
#include <avr/io.h>
2
#include <avr/delay.h>
3

4
#ifndef F_CPU
5
#define F_CPU 3686400UL
6
#endif
7

8
void WaitAndCheck(*i)
9
{
10
  int e; 
11
  for(e=100; e>0, e++)          // 100 x 10ms = 1s
12
  {
13
    _delay_ms(10);              // auch gut zur Entprellung
14
    if (PINB == 0b11111110)
15
    {
16
       while(PINB==0b11111110); // warte bis Taster losgelassen
17
       *i = *i + 10;            // aktueller zähler + 10
18
    }    
19
  }
20
}
21

22

23
int main (void)
24
{
25
  DDRA = 0xFF;         //Ausgang PORTA
26
  DDRB = 0x00;        //Eingang PORTB
27
  PORTA = 0xFF;        //LED Aus
28
  //unsigned char x;
29
  int i,e;
30
  while(1)
31
  {
32
    if (PINB == 0b11111110)
33
    {
34
      while(PINB==0b11111110);          // warte bis Taster losgelassen
35
      for(i=10; i>0; i--)
36
      {
37
        PORTA = 0b11111110;
38
        WaitAndCheck(&i);
39
      }
40
      PORTA = 0xFF;
41
      WaitAndCheck(&i);
42
    }
43
  }  
44
  return 0;
45
}
#2299192
Lesenswert?

Sebastian Haunei schrieb:
> Also sorry wenn ich hier auf Anfängerkram rumhacke, aber hab halt
> ziemlich wenig Ahnung...

Laß mal den ganzen Programmierkram beiseite, das kommt erst 10 Schritte 
später.
Du mußt erstmal damit anfangen, Dir eine Programmstruktur zu überlegen. 
Also auf welche Bedingung soll wie reagiert werden. Und das in ganz 
kleinen Schritten, also immer nur die nächste Aktion, sonst verhaspelst 
Du Dich nur (Spaghetticode).

Und dann mußt Du versuchen, zur Mainloop zurückkehrend zu denken. Also 
wenn eine Bedingung erfüllt ist, mache was, das ist ok.
Aber wenn die Bedingung nicht erfüllt ist, warten bis zum Sankt 
Nimmerleinstag, ist nicht ok. Das versaut Dir den ganzen Ablauf. Also 
zurück zur Mainloop und die nächste Aufgabe aufrufen.
Du hast schon mindenstens 3 Aufgaben, die sich nicht blockieren dürfen:
- Entprellen
- Taste auswerten
- Zeit ablaufen lassen

Am besten, schalte den PC aus und schreibe erstmal in Worten die ganzen 
Bedingungen und Aktionen auf (Programmablaufplan).
Erst wenn man weiß, wie etwas erfolgen soll, kann man es auch als Code 
einhacken.


Peter
Gast #2299194
Lesenswert?

Stimmt - so geht es natürlich auch. Aber für spätere Entwicklungen 
(falls das nicht die einzige Aufgabe des uC sein soll) und als 
Lerneffekt ist es mit dem Interrupt sicher besser.

Bei deiner Variante bleibt das Programm im Übrigen auch hängen, wenn man 
die Taste drückt. Weiterlaufen tut es erst, wenn man loslässt.
Gast #2299205
Lesenswert?

>Bei deiner Variante bleibt das Programm im Übrigen auch hängen, wenn man
>die Taste drückt. Weiterlaufen tut es erst, wenn man loslässt.

Natürlich müsste man sich überlegen und definieren, was das Program tun 
soll, wenn die Taste gedrückt bleibt:
 => jede Sekunde einmal um 10 hochzählen
 => alle 10ms um 10 hochzählen
 => warten bis Taste wieder losgelassen
OP #2299216
Lesenswert?

Hallo nochmal,
ihr müsst vielleicht wissen, der Controller soll beim Knopfdruck ein 
Relais anschalten und der Knopfdruck wird duch einen Münzeinwurf 
realisiert. Wie lange man jetzt auf dem Taster drücken muss dass etwas 
passiert kann man also praktisch garnicht beeinflussen, Der Münzeinwurf 
schaltet nur kurz einen Kontakt.
Und mein Gedanke war eben, nach dem Einwurf einer Münze schaltet das 
Relais durch und wenn von mir aus 15 Minuten vergangen sind schaltet das 
Relais wieder aus.
Das wäre an sich ja nicht schwierig zu realisieren, jedoch wollte ich 
dass man die Zeit, die das Relais durchschaltet beliebig durch erneutes 
Einwerfen verlängern kann.

Grüße Basti
OP #2299320
Lesenswert?

Entschuldigung Michael,
momentan habe ich den Atmega 8515 im STK500, ich programmiere mit GCC. 
Und was die Taktfrequenz angeht habe ich nur den Wert genommen dass die 
Zeit in meinem Programm, dass ich am Anfang hochgestellt habe, mit der 
Echtzeit übereinstimmt. Würde ich das nicht in den Kopf schreiben würde 
mein Controller zwar zählen, aber nicht in Echtzeit.

Mit dem Programm von peter komm ich auch nicht wirklich zurecht, probier 
seit Stunden rum, ich möchte es nun nochmal mit dem Interrupt versuchen, 
weil mir dein Programmschema sogar logisch erscheint :)

gruß Basti
Gast #2299344
Lesenswert?

Gut - dann würde ich zunächst einmal den Timer initialisieren.
Also in main vor der Endlosschleife (= Initialisirung):

[c]
  DDRA = 0xFF;         //Ausgang PORTA
  PORTA = 0xFF;        //LED Aus

  // Timer 0 konfigurieren
  TCCR0A = (1<<WGM01); // CTC Modus
  TCCR0B |= (1<<CS01); // Prescaler 8
  // ((1000000/8)/1000) = 125
  OCR0A = 125-1;

  // Compare Interrupt erlauben
  TIMSK |= (1<<OCIE0A);

  // Global Interrupts aktivieren
  sei();
[\c]

Über der Main Funktion eine globale Variable deklarieren:

[c]
volatile unsigned char toggle=0;
volatile unsigned int count=0;
[\c]

Dann machst du eine neue Funktion (unter der Main Funktion):

[c]
ISR (TIMER0_COMPA_vect)
{
  count ++;

  if (count == 1000)  // alle 1000 ms LED toggeln
  {
    if (toggle == 0)
    {
      PORTA = 0b11111110;
      toggle = 1;
    }
    else
    {
      PORTA = 0b11111111;
      toggle = 0;
    }
    count = 0,
  }
}
[\c]

Theoretisch sollte deine LED jetzt blinken. Bitte beachten, dass ich von 
einer Taktfrequenz von 1 MHz ausgegangen bin und die LED dann wohl 
schneller blinken wird, als 1x pro Sekunde. Im Übrigen hab ich das grade 
aus dem Kopf geschrieben und kann es in der Arbeit leider nicht 
austesten.

Bin dann mal in der Mittagspause.
#2299514
Lesenswert?

Sebastian Haunei schrieb:
> ihr müsst vielleicht wissen, der Controller soll beim Knopfdruck ein
> Relais anschalten und der Knopfdruck wird duch einen Münzeinwurf
> realisiert. Wie lange man jetzt auf dem Taster drücken muss dass etwas
> passiert kann man also praktisch garnicht beeinflussen, Der Münzeinwurf
> schaltet nur kurz einen Kontakt.
> Und mein Gedanke war eben, nach dem Einwurf einer Münze schaltet das
> Relais durch und wenn von mir aus 15 Minuten vergangen sind schaltet das
> Relais wieder aus.

Sag doch einfach, eine Parkuhr.
Wie schon oben gesagt, das sind 3 Aufgaben. Die kann man nacheinander 
implementieren und testen. Nicht alles zugleich machen und miteinander 
verwoben, sondern alles schön getrennt.

Warscheinlich willst Du die Zeit auch anzeigen, das ist dann die 4. 
Aufgabe:
1
int main()
2
{
3
  for(;;){
4
    // entweder:
5
      if( timerflag == 0 )    // warten auf Timer (Überlauf oder Compare)
6
        continue;
7
    // oder:
8
      _delay_ms( 10 );        // 10ms Zeitbasis mit Delay
9

10
    entprelle(();
11
    taste_gedrueckt_aktion(); // Ausgang an, zeit setzen
12
    zaehle_zeit();            // count down
13
    zeit_0_aktion();          // 0 erreicht
14
    anzeige_zeit();
15
  }
16
}
Und schon ist das Grundgerüst fertig.

Schreibe ruhig einzelne Funktionen. Das erleichtert die Übersicht und 
die Strukturierung.
Das bischen Stack für nen Funktionsaufruf kostet nix.


Peter
Gast #2299540
Lesenswert?

Ok, nun ist mir klar was Du genau bezwekst:

Ein Münzautomat, der pro Münzeinwurf für einen bestimmten Zeitkredit 
irgend was einschaltet, z.B. Warmwasser zum Duschen oder eine 
Waschmaschine...

Mein obiges Programm war wegen kleinen Flüchtigkeitsfehlern tatsächlich 
nicht kompilierbar, das folgende Beispiel sollte nun aber gehen...

Ev. noch zu berücksichtigen: für wieviele ms wird der Kontakt beim 
Münzeinwurf geschlossen? Ich gehe mal von >1 ms aus
1
#ifndef F_CPU
2
#define F_CPU 3686400UL
3
#endif
4

5
#include <avr/io.h>
6
#include <avr/delay.h>
7

8
#define Credit   (10)            // Zeitkredit pro Münze in Sekunden
9
#define Debounce (10)            // Prellzeit in ms
10

11
void WaitAndCheck(int *i);
12

13
int main (void)
14
{
15
  DDRA  = 0xFF;                  // Ausgang PORTA
16
  DDRB  = 0x00;                  // Eingang PORTB
17
  PORTA = 0xFF;                  // LED Aus
18
  int i;
19
  while(1)
20
  {
21
    if (PINB == 0b11111110)
22
    {
23
      _delay_ms(Debounce);       // zur Entprellung
24
      while(PINB==0b11111110);   // warte bis Taster losgelassen
25
      _delay_ms(Debounce);       // zur Entprellung
26
      PORTA = 0b11111110;        // einschalten
27
      for(i=Credit; i>0; i--)
28
      {
29
        WaitAndCheck(&i);
30
      }
31
      PORTA = 0b11111111;        // ausschalten
32
    }
33
  }
34
  return 0;
35
}
36

37
void WaitAndCheck(int *i)
38
{
39
  int e;
40
  for(e=1000; e>0; e--)          // 1000 x 1ms = 1s
41
  {
42
    _delay_ms(1);
43
    if (PINB == 0b11111110)
44
    {
45
      _delay_ms(Debounce);       // zur Entprellung
46
      while(PINB==0b11111110);   // warte bis Taster losgelassen
47
      _delay_ms(Debounce);       // zur Entprellung
48
      *i = *i + Credit;          // aktueller zähler + 10
49
      e = e-(Debounce*2);        // Korrektur für Prellwartezeit
50
    }
51
  }
52
}
Gast #2299553
Lesenswert?

Füg mal bitte "PORTA = 0b11111110;" direkt nach "counter++;" ein und 
kommentiere den Rest der Funktion (vom "if" bis "}") aus. Dann sehen 
wir, ob er überhaupt in die Interrupt-Routine reingeht.

Wie gesagt - ich kanns hier leider nicht testen, sonst würde es 
schneller gehen.

Kannst du deine LED so grundsätzlich anschalten? PORTA = 0b11111110;

Vg
#2299579
Lesenswert?

Sebastian Haunei schrieb:
> @Peter
> Ich weiß mit einzelnen Teilen leider nichts anzufangen

Das ist ja nur das Grundgerüst, damit die einzelnen Aufgaben klar 
werden.
Die einzelnen Funktionen erfüllt man dann später mit Leben 
(Top-Down-Methode).

Zum Austausch der Informationen zwischen den Funktionen benutzt man 
Argumente, Returnwerte oder globale Variablen.
Der Count-Down-Zähler ist z.B. eine globale Variable.
Der Erfolg (Taste gedrückt erkannt) von entprellen() ist z.B. der 
Returnwert.


Peter
#2299582
Lesenswert?

Sebastian Haunei schrieb:
> es leuchtet keine LED
> er schreibt:
>
> #warning "This header file is obsolete. Use <avr/interrupt.h>
>
> aber so hab ich´s ja geschrieben

Dann korriegiere es.
In der Fehlermeldung steht am linken Rand, dass sie sich auf das Include 
File signal.h bezieht.
Und die Fehlermeldung lautet, dass dieses File obsolet (als nicht mehr 
notwendig ist) und du statt dessen interrupt.h benutzen sollst.

Was also ist dein Vorschlag, was du da tun könntest?


Zum Problem:
1
  // Timer 0 konfigurieren
2
  TCCR1A = (1<<WGM01); // CTC Modus
3
  TCCR1B |= (1<<CS01); // Prescaler 8

du hast einen Mega 8535, richtig?

Dann ist das falsch.,
Für CTC muss WGM12 gesetzt sein und dieses Bit steht im Register TCCR1B
1
  // Timer 0 konfigurieren
2
  TCCR1B |= (1<<WGM12) | (1<<CS01); // CTC, Prescaler 8
 ´
Mit der Originaleinstellung war das kein CTC MOdus, sondern ein PWM 
Modus. Und der war ca. um einen Faktor 524 zu lang. Wenn du also rund 10 
Minuten gewartet hättest, hätte die LED getoggelt.


Lies das AVR-GCC-Tutorial zum Theme Timer. Und dann lies das 
Datenblatt, Abschnitt Timer. Das Datenblatt ist deine letztendliche 
'Bibel' der du trauen kannst.
UNd schau dir anderen Code an. Timer sind wirklich sehr einfach. Aber 
irgendwie hab ich das Gefühl, auch du hast mitlerweile in einen "bitte 
betet mir das vor, damit ich nichts selber machen muss" Modus 
geschaltet. Ist leider gang und gäbe hier im Forum.
#2299586
Lesenswert?

Michael schrieb:
> Füg mal bitte "PORTA = 0b11111110;" direkt nach "counter++;" ein und
> kommentiere den Rest der Funktion (vom "if" bis "}") aus. Dann sehen
> wir, ob er überhaupt in die Interrupt-Routine reingeht.
>
> Wie gesagt - ich kanns hier leider nicht testen, sonst würde es
> schneller gehen.

Du hast dich mit dem Konfigurationsbits des Timers vertan.

Ich geb jetzt wieder zurück an dich :-)
Mach weiter so. Du kriegst ihn schon noch soweit, dass er Timer 
versteht. Nur bete es ihm nicht zu detailiert vor. Ein bischen 
nachdenken muss er selber auch.


so ists laut Datenblatt Mega8535 richtig
1
  // Timer 0 konfigurieren
2
  TCCR1B |= (1<<WGM12) | (1<<CS01); // CTC, Prescaler 8
verifiziert im Simulator.
OP #2299620
Lesenswert?

@ Karl Heinz
Danke mal und ich weiß, am liebsten würde ich sagen schreibt mir mal 
bitte dieses und jenes Programm, ist ja auch verlockend wenn am anderen 
Ende lauter Profiprogrammierer sitzen...:)
Aber natürlich will ich auch was dabei lernen.

@ Michael, habs umgeschrieben, die LED leuchtet konstant

ich hab glaub ich schon wieder Fragen überlesen
@ Peter, ich schau mir dein Programm auch noch genauer an, nur muss ich 
mich momentan auf eine Sache konzentrieren, Danke trotzdem mal
Gast #2299640
Lesenswert?

Ja - jetzt musst du natürlich die Zeile "PORTA = 0b11111110;" unter 
"counter ++;" wieder rausnehmen. Das war nur testweise, ob die Funktion 
überhaupt aufgerufen wird.

Wenn die LED blinkt, sollten wir als nächstes rauskriegen, mit wieviel 
MHz dein Prozessor denn nun läuft.
#2299648
Lesenswert?

Karl Heinz Buchegger schrieb:
> Bitte häng deinen Code nicht als txt File an.
>
> Nimmm dein *.c File so wie es ist und häng es als Attachment an.
> Der Vorteil: Wir alle kommen in den Genuss von C-Syntax Highlighting.
> Und Arbeit ist es für dich auch weniger.

Vergleich mal deinen Anhang mit dem hier angehängten.
Das ist dann schon ein Unterschied.
Angehängte Dateien:
#2299653
Lesenswert?

Michael schrieb:
> Ich vermute mal, dass der Takt vom STK500 kommt. Ich kenne das STK500
> nicht wirklich, aber ich meine, da kann man per Software die Frequenz
> einstellen?

Es soll dir einfach sagen, wie lang die Hell Dunkel Zyklen sind.

Wahrscheinlich hat er 1Mhz und die LED brennt 1 Sekunde und ist 1 
Sekunde dunkel.

Das reicht dann schon. Für seine Zwecke genau genug. Du brauchst nur die 
Zahl, damit du damit rechnen kannst.

(BTW: ich würde dann auf 10 oder gar 100 ms IRQ Frequenz gehen. Mit 1 ms 
gehts mit dem counter sonst nur bis 65 Sekunden hoch und das wird ein 
wenig wenig werden.)
Was hast du dir fürs Entprellen vorgestellt? debounce-Makro?
Gast #2299660
Lesenswert?

Karl Heinz Buchegger schrieb:
> Wahrscheinlich hat er 1Mhz und die LED brennt 1 Sekunde und ist 1
> Sekunde dunkel.

Daran zweifel ich nur ein bisschen, weil er in seinem Programm 3,6 MHz 
eingegeben hat und meinte, die Zahl durch Versuch ermittelt zu haben.


Karl Heinz Buchegger schrieb:
> ich würde dann auf 10 oder gar 100 ms IRQ Frequenz gehen.

Da hast du sicherlich recht.

Karl Heinz Buchegger schrieb:
> Was hast du dir fürs Entprellen vorgestellt? debounce-Makro?

Joah - da würde ich das Makro von Peter nehmen.
#2299697
Lesenswert?

@Michael

Die große Frage ist jetzt:
Hat er verstanden, was da jetzt abgeht?

Im Moment zweifle ich da noch. Wenn ich einen Vorschlag machen darf: 
Sieh zu, dass du ihm nahebringst wie ein Timer generell arbeitet (der 
zählt vor sich hin), was es mit diesen Compare Sachen auf sich hat (bei 
bestimmten Zählerständen kann man etwas auslösen lassen) und was ein CTC 
Modus ist (nicht nur auslösen, sondern den Timer auch auf 0 
zurücksetzen).

Im Idealfall ist er in der Lage, aus dem Verständnis der Arbeitsweise 
heraus, die Compare Konstanten zu berechnen.

Ich fänds schade, wenn er am Ende mit einem Programm dasteht und nicht 
weiß, wie es eigentlich funktioniert.
#2300909
Lesenswert?

Schau Dir mal das hier an, das ist von der Aufgabenstellung sehr 
ähnlich, also leicht anzupassen:

Beitrag "Re: LED Programm"

Der Trick ist, daß die Mainloop mit einem Delay in einem festen 
Zeitraster ausgeführt wird. Dadurch kann man mit Zählern ganz leicht 
beliebige Zeitabläufe realisieren. Ganz ohne Interrupts und Timer.
Die 10ms sind so kurz, daß sie den Ablauf für den Menschen nicht merkbar 
behindern.


Peter

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren