Forum: Mikrocontroller und Digitale Elektronik Variable nach Zeit setzen


von Lokus P. (derschatten)


Lesenswert?

Bräuchte mal wieder einen Tip.

Ich möchte gerne eine Variable nach einer bestimmten Zeit (10 sek) auf 1 
setzen.
Das ganze soll jedoch im Hintergrund laufen.

Der Zähler soll jedoch immer auf 0 gesetzt werden sobald eine Taste oder 
ein Drehgeber betätigt wird.

Kann ich das mit einer IF-Abfrage machen?
1
  if(timer != 0)
2
  {
3
    ntimer++;
4
    while(ntimer < 100)
5
    {
6
      ende = 1;
7
      timer = 0;
8
    }
9
  }

Mir ist jedoch noch nicht klar welchen Interrupt ich dafür nehmen soll.

Kann ich das in ein ISR(TIMER0_OVF_vect) stecken?

Verwendet wird ein ATMEGA 8

von Karl H. (kbuchegg)


Lesenswert?

Manfred W. schrieb:
> Bräuchte mal wieder einen Tip.
>
> Ich möchte gerne eine Variable nach einer bestimmten Zeit (10 sek) auf 1
> setzen.
> Das ganze soll jedoch im Hintergrund laufen.

Klarer Fall für einen Timer mit einer zugehörigen ISR.

> Der Zähler soll jedoch immer auf 0 gesetzt werden sobald eine Taste oder
> ein Drehgeber betätigt wird.

Ja, kann man machen.

> Kann ich das mit einer IF-Abfrage machen?
>
>   if(timer != 0)

wo kommt die Variable 'timer' her?

>   {
>     ntimer++;
>     while(ntimer < 100)

No. while Schleife willst du da überhaupt nicht haben. Dazu hast du ja 
eine ISR, die regelmässig aufgerufen wird. WEnn die ISR zu oft 
aufgerufen wird (zb alle 1 Sekunde), kann man immer noch in der ISR 
einen Zähler mitführen und erst beim 10.ten Aufruf das gewünschte tun. 
Dann hat man ebenfalls eine Verzögerung von 10 Sekunden.


> Mir ist jedoch noch nicht klar welchen Interrupt ich dafür nehmen soll.

Der mit dem du eine ISR Aufruffrequenz bekommst, mit der du gut rechnen 
kannst.

>
> Kann ich das in ein ISR(TIMER0_OVF_vect) stecken?

Sicher kannst du.
Du musst nur ausrechnen bzw. den Timer so einstellen, dass die ISR 
entsprechend oft und möglichst mit einem für dich brauchbaren Zeitraster 
aufgerufen wird.
Hinweis: das werden nicht 10 Sekunden sein, sondern schneller. Aber das 
ist ja kein Problem. Auch mit einer Uhr auf der nur 1 Sekundenzeiger 
ist, kann man schliesslich 1 Minute abzählen. Man muss nur 60 
Sekundenticks abwarten. Und genau das gleiche kann man auch mit einer 
Timer-ISR machen
1
volatile uint8_t timeCount;
2
3
ISR( TIMER0_OVF_vect )
4
{
5
  if( timeCount > 0 ) {
6
    timeCount--;
7
8
    if( timeCount == 0 )
9
      mache was auch immer zu machen ist
10
  }
11
}

setzt du die Variable timeCount auf einen Wert ungleich 0, so wird diese 
variable bei jedem Aufruf der ISR um 1 heruntergezählt. Nach 
entsprechend vielen Aufrufen wird die irgendwann zu 0 und wenn das der 
Fall ist, löst dann die ISR die Aktion aus.
Wird die ISR jede Sekunde aufgerufen und setzt du timeCount auf 10, dann 
erfolgt die Aktion daher 10 Sekunden nachdem du timeCount auf 10 gesetzt 
hast. timeCount arbeitet also wie ein Küchentimer, den man auf eine Zeit 
einstellt und der langsam bis auf 0 zurückzählt, ehe die Glocke bimmelt 
und anzeigt, dass die Eier fertig sind.

Und das alles im Hintergrund und ohne das sich die Hauptschleife darum 
kümmern müsste.

von Lokus P. (derschatten)


Lesenswert?

Das sind eine Menge Infos, danke das du dir die Mühe gemacht hast.

Also die Variable 'timer' ist dafür gedacht, den Timer nur bei Bedarf zu 
starten. Also wenn man sich in einem Untermenüpunkt befindet.

Die Zeitspanne für den ISR kann ich für diese Zwecke nicht ändern da in 
der gleichen Routine auch die Abfrage des Tastendruckes passiert.

Vielleicht könnte ich das gleich dazu nutzen den Timer zu resetieren?
1
ISR(TIMER0_OVF_vect)
2
{
3
  static uint8_t ct0, ct1, rpt, timeCount;
4
  uint8_t i;
5
6
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);
7
8
  i = key_state ^ ~ENCODER_PIN;                // Taste gewechselt ?
9
  ct0 = ~(ct0 & i);                      // Reset oder zähle ct0
10
  ct1 = ct0 ^ (ct1 & i);                    // Reset oder zähle ct1
11
  i &= ct0 & ct1;                        // count until roll over ?
12
  key_state ^= i;                        // then toggle debounced state
13
  key_press |= key_state & i;                  // 0 -> 1: Tastendruck registrieren
14
15
  if( (key_state & ENC_TASTER) == 0)              // Überprüfe Wiederholfunktion
16
    rpt = REPEAT_START;                    // Verzögerung starten
17
  if(--rpt == 0)
18
  {
19
    rpt = REPEAT_NEXT;                    // Wiederhole Verzögerung
20
    key_rpt |= key_state & ENC_TASTER;
21
  }
22
23
  if(timer != 0)
24
  {
25
    if(timeCount > 0)
26
    {
27
      timeCount--;
28
29
    if(timeCount == 10)
30
31
      ende = 1;
32
      timer = 0;
33
    }
34
  }
35
}

Ich müßte also auf eine Verzögerung zurückgreifen.

von Karl H. (kbuchegg)


Lesenswert?

Manfred W. schrieb:
> Also die Variable 'timer' ist dafür gedacht, den Timer nur bei Bedarf zu
> starten.

OK.
Ich denke du hast schon gesehen, dass das gar nicht notwendig ist. Der 
Timer kann ruhig ständig laufen, das schadet nicht. Er zählt seine 
Zählvariable ja nur dann runter, wenn die ungleich 0 ist.

> Die Zeitspanne für den ISR kann ich für diese Zwecke nicht ändern da in
> der gleichen Routine auch die Abfrage des Tastendruckes passiert.

Ja, macht ja nichts.
Dann hast du eben eine andere Zeitkonstante und musst die Zählvariable 
mit einem anderen Wert belegen um auf 10 Sekunden zu kommen.

> Vielleicht könnte ich das gleich dazu nutzen den Timer zu resetieren?

lass den Timer in Ruhe

> ISR(TIMER0_OVF_vect)
> {
>   static uint8_t ct0, ct1, rpt, timeCount;
>   uint8_t i;
>
>   TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);
>
>   i = key_state ^ ~ENCODER_PIN;                // Taste gewechselt ?
>   ct0 = ~(ct0 & i);                      // Reset oder zähle ct0
>   ct1 = ct0 ^ (ct1 & i);                    // Reset oder zähle ct1
>   i &= ct0 & ct1;                        // count until roll over ?
>   key_state ^= i;                        // then toggle debounced state
>   key_press |= key_state & i;                  // 0 -> 1: Tastendruck
> registrieren
>
>   if( (key_state & ENC_TASTER) == 0)              // Überprüfe
> Wiederholfunktion
>     rpt = REPEAT_START;                    // Verzögerung starten
>   if(--rpt == 0)
>   {
>     rpt = REPEAT_NEXT;                    // Wiederhole Verzögerung
>     key_rpt |= key_state & ENC_TASTER;
>   }
>
>   if(timer != 0)
>   {
>     if(timeCount > 0)
>     {
>       timeCount--;
>
>     if(timeCount == 10)

Du hast da etwas misverstanden.
Diese Variable ist ein Rückwärtszähler!
So wie die Schaltuhren auf dem WC.

Du stellst eine Zeit ein und die Uhr läuft rückwärts auf 0.
Und wenn 0 erreicht ist, dann schaltet die Uhr die Lüftung ab.

      if( timeCount == 0 )

von Lokus P. (derschatten)


Lesenswert?

Cool, das funktioniert schon mal halbwegs. Es reagiert nur etwas seltsam 
wenn man vor ablauf der Zeit bereits wieder zurückgesprungen ist. Dann 
muß man den taster 2 mal betätigen bis man wieder im Menü landet.

1
static int16_t timeCount = 0;
2
3
ISR(TIMER0_OVF_vect)
4
{
5
  static uint8_t ct0, ct1, rpt;
6
  uint8_t i;
7
8
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);
9
10
  i = key_state ^ ~ENCODER_PIN;                // Taste gewechselt ?
11
  ct0 = ~(ct0 & i);                      // Reset oder zähle ct0
12
  ct1 = ct0 ^ (ct1 & i);                    // Reset oder zähle ct1
13
  i &= ct0 & ct1;                        // count until roll over ?
14
  key_state ^= i;                        // then toggle debounced state
15
  key_press |= key_state & i;                  // 0 -> 1: Tastendruck registrieren
16
17
  if( (key_state & ENC_TASTER) == 0)              // Überprüfe Wiederholfunktion
18
    rpt = REPEAT_START;                    // Verzögerung starten
19
  if(--rpt == 0)
20
  {
21
    rpt = REPEAT_NEXT;                    // Wiederhole Verzögerung
22
    key_rpt |= key_state & ENC_TASTER;
23
  }
24
25
  if(timeCount > 0)
26
  {
27
    timeCount--;
28
  if(timeCount == 0)
29
    ende = 1;
30
  }
31
}
32
....
33
34
int main(void)
35
{
36
  int32_t val = 0;
37
  ioinit();
38
  eeprom_var();
39
  encode_init();
40
41
  sei();                            // Interrupts aktivieren
42
43
  for(;;)
44
  {
45
46
  LEDS_PORT = laufwerkidled | marker;
47
48
    if(get_key_short(ENC_TASTER))
49
    {
50
      timeCount = 300;          // entspricht ungefähr 5sek
51
      encode_read();
52
      while(ende != 1)
53
      {
54
        val += encode_read();
55
56
        if(nSelect == 0)                // Haupt-Menü
57
        {
58
          if (val > 3) val = 3;
59
          if (val < 0) val = 0;
60
        }
61
62
        if(nSelect == 1)                // Floppy-ID Menü
63
        {
64
          if (val > 5) val = 5;
65
          if (val < 4) val = 4;
66
        }
67
68
        if(nSelect == 2)                // Kernal-Menü
69
        {
70
          if (val > 9) val = 9;
71
          if (val < 6) val = 6;
72
        }
73
74
        if(nSelect == 3)                // Schreibschutz-Menü
75
        {
76
          if (val > 11) val = 11;
77
          if (val < 10) val = 10;
78
        }
79
80
        switch(val)
81
        {
82
          // Hauptmenü
83
          case 0:
84
            LEDS_PORT = _I;              // I -> ID
85
            if(get_key_short(ENC_TASTER))
86
            {
87
              val = 4;
88
              nSelect = 1;
89
            }
90
            break;
91
....
92
93
        }
94
      }
95
      ende = 0;
96
    }
97
  }
98
}

Wie reagiere ich nun am besten auch auf den Encoder?

von Karl H. (kbuchegg)


Lesenswert?

Genau gleich.
Jedesmal, wenn du eine Bentuzeraktion (Taste gedrückt, Encoder gedreht) 
feststellst, setzt du den timeCount wieder auf 300

Taste gedrückt ist glaub ich klar.
Am Encoder wurde gedreht, wenn encode_read() etwas anderes als 0 zurück 
liefert.

   Also

   tmp = encode_read();
   if( tmp != 0 )
     timeCount = 300;
   val += tmp;


Du kannst auch eine Hilfsfunktion benutzen:

int8_t encode_read_timeout()
{
  int8_t tmp = encode_read();
  if( tmp != 0 )
    timeCount = 300;
  return tmp;
}

und dann einfach encode_read_timeout() anstelle von encode_read() 
benutzen

    if(get_key_short(ENC_TASTER))
    {
      timeCount = 300;          // entspricht ungefähr 5sek

      encode_read_timeout();

      while(ende != 1)
      {
        val += encode_read_timeout();
        ....


Wichtig ist nur, dass du nach JEDER Benutzeraktion den Timer auch wieder 
auf seine 300 setzt. Am einfachsten geht das mit solchen 
Hilfsfunktionen, dann kann man das nicht vergessen.

Und wenn kein Timeout mehr notwendig ist, dann setzt du timeCount ganz 
einfach auf 0 und der Mechanismus kommt ganz von alleine zum Stillstand.

von Karl H. (kbuchegg)


Lesenswert?

> Es reagiert nur etwas seltsam
> wenn man vor ablauf der Zeit bereits wieder zurückgesprungen ist. Dann
> muß man den taster 2 mal betätigen bis man wieder im Menü landet.

Das hat aber nichts mit dem Timeout zu tun, sondern damit wie du die 
while Schleife da drüber gelegt hast, und dass du aus der nur raus 
kommst, wenn ende gleich 1 geworden ist.

von Lokus P. (derschatten)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Das hat aber nichts mit dem Timeout zu tun, sondern damit wie du die
>
> while Schleife da drüber gelegt hast, und dass du aus der nur raus
>
> kommst, wenn ende gleich 1 geworden ist.

Ja, da ist irgendwas durcheinander.
Der Timer funktioniert zumindest schon mal.

von Lokus P. (derschatten)


Lesenswert?

Könnte mir da vielleicht jemand noch unter die Arme greifen.

Wie gesagt, die Zeitschleife funkltioniert perfekt.
Das Programm wechselt jedoch nicht korrekt in die gewünschte Menüebene.
Nach zeitablauf springt er mir nur eine Ebene zurück und wenn ichw eiter 
Navigieren möchte werden auch sporadisch verschiedene Menüs 
angesprungen.

von Lokus P. (derschatten)


Lesenswert?

Hab den Fehler gefunden.
timeCount darf natürlich nicht auf 0 gesetzt werden sondern auf 1. Sonst 
wird die Variable ende nie auf 1 gesetzt.

Das einzige was nun jedoch nicht mehr funktioniert ist das sich der 
Encoderwert encode_read() nicht mehr zurücksetzen läßt.

So springt er mir immerwieder in das zuletzt aufgerufene Menü nach dem 
Tastendruck.

von Karl H. (kbuchegg)


Lesenswert?

Manfred W. schrieb:
> Hab den Fehler gefunden.
> timeCount darf natürlich nicht auf 0 gesetzt werden sondern auf 1. Sonst
> wird die Variable ende nie auf 1 gesetzt.

Tip:
Nichts und niemand hindert dich daran, die Variable ende auch ausserhalb 
der ISR auf 1 zu setzen. Zb dann, wenn der Benutzer einen Menüpunkt 
angewählt hat mit dem das Menü abgebrochen wird. Du musst das nicht über 
den Timer regeln.

> So springt er mir immerwieder in das zuletzt aufgerufene Menü nach dem
> Tastendruck.


Ich denke du bist langsam an einem Punkt an dem du merkst, dass eine 
Modularisierung deines Programmes in einzelne Funktionen gut tun würde. 
Du veränderst unabsichtlich zu viele Dinge, weil du alles in einer 
Hauptschleife hast und den Überblick verlierst.

Jedes einzelne Menü als eigene Funktion wäre schon mal ein Anfang.

von Lokus P. (derschatten)


Lesenswert?

Daran hatte ich schon gedacht, will mir das aber jetzt nicht mehr antun 
alles umzustricken. Abgeshen davon ist das Programm schon mehr oder 
weniger fertig.

In den Funktionen müßte ich dann ja wieder mit SWITCH CASE hantieren.

Bis auf diesen kleinen Schönheitsfehler mit dem Encoder läuft es schon 
so wie ich gern möchte.

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.