Forum: Mikrocontroller und Digitale Elektronik AVR-GCC Tutorial Taster entprellen


von Lars E. (larse)


Lesenswert?

Hallo allerseits,

vielleicht möchte ja jemand mir meine Frage beantworten, auch wenn das 
Thema schon zig mal diskutiert wurde, nur leider werde ich daraus noch 
nicht ganz schlau.

Ich habe versucht das Programm aus dem Tutorial zum Tasten entprellen zu 
schreiben, habs dann in meinen Atmega16 geladen und versucht zum laufen 
zu bringen, klappt nur leider nicht.

Könnte sich vielleicht mal jemand zum Quellcode äußern?

#include <avr/io.h>
#include <inttypes.h>
#ifndef F_CPU
#define F_CPU 16000000                /*Quarz 16 Mhz*/
#endif
#include <util/delay.h>
                        /*Funktion zum entprellen*/
inline uint8_t warte(volatile uint8_t *port, uint8_t pin)
  {
                        /*Pin auf Masse gezogen, warten*/
  if (! (*port & (1 << pin)))
      {
      _delay_ms(15);
      _delay_ms(15);
      _delay_ms(15);
      _delay_ms(15);
      _delay_ms(15);

    if (*port & (1 << pin))  /*Zeit zum loslassen*/
          {
            _delay_ms(15);
            _delay_ms(15);
            _delay_ms(15);
            _delay_ms(15);
            return 1;
          }

      }
    return 0;

  }

int main(void)
{
DDRB &= ~(1 << PB0);      /*Pin PB0 auf Eingang(Taster)*/
PORTB &= ~(1 <<PB0);      /*PullUp Widerstand aus, da extern*/

if (warte(&PINB, PB0))      /*Wenn Taster an PB0 gedrückt..*/
      {
  PORTD = PIND ^ (1 << PD6);  /*LED an Port PD6 an bzw. aus*/
  }
  return 0;
}


Vielen Dank!

Lars

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Es fehlt die Einrichtung des PORTD als Ausgabeport. Es muss eine Zeile 
mit DDRD in den Code...

von Lars E. (larse)


Lesenswert?

Stefan "stefb" B. wrote:
> Es fehlt die Einrichtung des PORTD als Ausgabeport. Es muss eine Zeile
> mit DDRD in den Code...


So, dass hab ich mal geändert, dürfte ja eigentlich so sein:
int main(void)
{
DDRB &= ~(1 << PB0);      /*Pin PB0 auf Eingang(Taster)*/
PORTB &= ~(1 <<PB0);      /*PullUp Widerstand aus, da extern*/

if (warte(&PINB, PB0))      /*Wenn Taster an PB0 gedrückt..*/
      {
  DDRD = 0xff;        /*PortD Als Ausgang*/
  PORTD = PIND ^ (1 << PD6);  /*LED an Port PD6 an bzw. aus*/
  }
  return 0;
}



Funktionieren tus damit leider imme rnoch nicht, ich bin mir nicht ganz 
sicher, ob in der Warte-Funktion nicht einmal ein return fehlt?

Gruß lars

von Johannes M. (johnny-m)


Lesenswert?

> PORTD = PIND ^ (1 << PD6);  /*LED an Port PD6 an bzw. aus*/
Das kann auch schiefgehen, wenn andere Pins des Port verwendet werden. 
Wenn ein Ausgang umgeschaltet werden soll, immer das PORT -Register 
lesen, weil nur darin der aktuelle Zustand des Ausgangstreibers abgelegt 
ist.

Wenn andere Pins an dem Port als Eingänge geschaltet sind, kann das oben 
zum munteren Pull-Up-ein-und-ausschalten führen. Also entweder (z.B. 
beim Mega16):
1
PORTD = PORTD ^ (1 << PD6);  //LED an Port PD6 toggeln
oder (bei neueren AVRs, die über das PIN-Register toggeln können, indem 
man eine 1 hineinschreibt)
1
PIND = 1 << PD6;  //LED an Port PD6 toggeln

Aber warum machst Du die Initialisierung von DDRD in der if-Abfrage? Die 
gehört an den Anfang von main.

Und der dickste Bock überhaupt ist, dass Du die Abfrage nur genau einmal 
durchführst und der µC danach gar nichts mehr macht! Es fehlt eine 
Endlosschleife am Ende von main.

von Lars E. (larse)


Lesenswert?

Aha,
danke für die hinweise.

Hier mal die neue Version des Hauptprogramms, wie gesagt, ich fange grad 
erst an mich mit AVR's zu beschäftigen, also verzeiht mir meine 
Unwissenheit.

int main(void)
{
DDRB &= ~(1 << PB0);      /*Pin PB0 auf Eingang(Taster)*/
PORTB &= ~(1 <<PB0);      /*PullUp Widerstand aus,da extern*/

DDRD |= (1 << PD6);        /*PD6 als Ausgang*/

  while (1)
  {
  if (warte(&PINB, PB0))    /*Wenn Taster an PB0 gedrückt..*/
  {
  PORTD = PORTD ^ (1 << PD6);  /*LED an Port PD6 toggeln*/
  }
  return 0;

  }
}


Funktion ist nach wie vor nicht gegeben, aber ich werds weiterhin 
versuchen.

Gruß Lars

von Karl H. (kbuchegg)


Lesenswert?

Lars Elsner wrote:

> Funktion ist nach wie vor nicht gegeben, aber ich werds weiterhin
> versuchen.

Fang jetzt gleich damit an, dir eine vernünftige Code
Formatierung anzugewöhnen. Dazu gehört auch ein konsistentes
Einrückschema

Dein Code mal umformatiert
1
int main(void)
2
{
3
  DDRB &= ~(1 << PB0);      /*Pin PB0 auf Eingang(Taster)*/
4
  PORTB &= ~(1 <<PB0);      /*PullUp Widerstand aus,da extern*/
5
6
  DDRD |= (1 << PD6);        /*PD6 als Ausgang*/
7
8
  while (1)
9
  {
10
    if (warte(&PINB, PB0))    /*Wenn Taster an PB0 gedrückt..*/
11
    {
12
      PORTD = PORTD ^ (1 << PD6);  /*LED an Port PD6 toggeln*/
13
    } // end if
14
15
    return 0;
16
  }  // end while
17
} // end main

Und jetzt meine Frage:
Was glaubst du wird mit deiner Endlosschleife passieren, wenn
mitten in der Endlosschleife der return (also das Beendigen
der Funktion main()) steht?

von Stefan B. (stefan) Benutzerseite


Lesenswert?

LED und Taster sind richtig angeschlossen? Die LED kannst und solltest 
du testen. Im Moment bin ich auf dem Blinken-Trip, daher empfehle ich 
diese Variante ;-)
1
int main(void)
2
{
3
  uint8_t t, i;
4
5
  DDRB &= ~(1 << PB0);      /*Pin PB0 auf Eingang(Taster)*/
6
  PORTB &= ~(1 <<PB0);      /*PullUp Widerstand aus,da extern*/
7
8
  DDRD |= (1 << PD6);       /*PD6 als Ausgang*/ 
9
10
  // 10 mal blinken mit 0,5s LED AN + 0,5s LED AUS
11
  for (i = 0; i < 10; i++)
12
  {
13
    PORTD |= (1 << PD6);  
14
    for (t = 0; t < 50; t++)
15
      _delay_ms(10);
16
17
    PORTD &= ~(1 << PD6);  
18
    for (t = 0; t < 50; t++)
19
      _delay_ms(10);
20
  }
21
22
  while (1)
23
  {
24
    if (warte(&PINB, PB0))    /*Wenn Taster an PB0 gedrückt..*/
25
    {
26
      PORTD = PORTD ^ (1 << PD6);  /*LED an Port PD6 toggeln*/
27
    }
28
  }
29
30
  return 0; // <=== nicht im while-Block!
31
}

Fragen:

1/ Kommt dein Aufbau auf 10x Blinken in 10 Sekunden oder blinkt es 
deutlich langsamer? Damit kann man Rückschlusse auf die eingestellte 
Taktquelle und deine Compiler-Eistellungen beim Übersetzen (Optimierung 
an/aus) ziehen. Bei falscher Einstellung müsstesr du den Taster statt 
75ms im Extremfall 1,2s und länger halten!

2/ Nach dem Blinken ohne Tastendruck: Ist die LED AN oder AUS? Damit 
kann man Rückschlusse auf den Anschluss der LED ziehen.

3/ Wie ist dein Taster angeschlossen (Schaltbild)?

von Lars E. (larse)


Lesenswert?

Karl heinz Buchegger wrote:
> Und jetzt meine Frage:
> Was glaubst du wird mit deiner Endlosschleife passieren, wenn
> mitten in der Endlosschleife der return (also das Beendigen
> der Funktion main()) steht?


Ah, jetzt seh ichs auch. Funktionieren tut es jetzt schonmal, allerdings 
wird nicht jeder Tastendruck ausgeführt, manchmal passiert einfach 
nichts.

Vielen Dank schonmal dafür.
Gruß Lars

von TorstenS (Gast)


Lesenswert?

Hallo,

das Problem liegt in der warte-Funktion.
Die kehrt nur mit 1 zurück, wenn der Pegelwechsel genau innerhalb der 
ersten 5 delay-Aufrufe erfolgt.

Tschüss
Torsten

von Karl H. (kbuchegg)


Lesenswert?

Lars Elsner wrote:
> Karl heinz Buchegger wrote:
>> Und jetzt meine Frage:
>> Was glaubst du wird mit deiner Endlosschleife passieren, wenn
>> mitten in der Endlosschleife der return (also das Beendigen
>> der Funktion main()) steht?
>
>
> Ah, jetzt seh ichs auch.

Da siehst du, was eine durchgezogene Einrückung ausmachen kann :-)


> Funktionieren tut es jetzt schonmal, allerdings
> wird nicht jeder Tastendruck ausgeführt, manchmal passiert einfach
> nichts.

Das liegt daran, dass diese Art der Entprellung ziemlicher
Mist ist. Nimm es als Anregung um das Problem der Entprellung
zu erkennen, mehr aber nicht. Für ein praktisches Programm ist
diese Entprellung sowieso nicht zu gebrauchen.

von Lars E. (larse)


Lesenswert?

Karl heinz Buchegger wrote:
> Das liegt daran, dass diese Art der Entprellung ziemlicher
> Mist ist. Nimm es als Anregung um das Problem der Entprellung
> zu erkennen, mehr aber nicht. Für ein praktisches Programm ist
> diese Entprellung sowieso nicht zu gebrauchen.


Das konnte ich bis jetzt noch nicht beurteilen, daher bin ich davon 
ausgegangen, dass es ein gebräuchlicher weg wäre. Ich war zuvor schon 
auf diesen Beitrag Entprellung gestoßen, bin da aber noch nicht ganz 
mit durch, sprich ich grübel noch über den Code.

Was wäre denn eine gebräuchliche und sinnvolle Methode Taster 
abzufragen?

Gruß Lars

von Karl H. (kbuchegg)


Lesenswert?

Lars Elsner wrote:
> Karl heinz Buchegger wrote:
>> Das liegt daran, dass diese Art der Entprellung ziemlicher
>> Mist ist. Nimm es als Anregung um das Problem der Entprellung
>> zu erkennen, mehr aber nicht. Für ein praktisches Programm ist
>> diese Entprellung sowieso nicht zu gebrauchen.
>
>
> Das konnte ich bis jetzt noch nicht beurteilen, daher bin ich davon
> ausgegangen, dass es ein gebräuchlicher weg wäre. Ich war zuvor schon
> auf diesen Beitrag Entprellung gestoßen, bin da aber noch nicht ganz
> mit durch, sprich ich grübel noch über den Code.
>
> Was wäre denn eine gebräuchliche und sinnvolle Methode Taster
> abzufragen?

Die PeDa Komfort Methode, wie sie in deinem Beitrag beschrieben
ist, ist schon eine sehr gute Methode. Nimm sie am Anfang als
'Black-Box', die du so wie sie ist einfach benutzt. Diese
Entprellfunktion ist ziemlich trickreich aufgebaut und es wird
sicherlich noch eine Zeit dauern, bis du da durchblickst. Das geht
nicht gegen dich, auch gestandene Programmierer durchschauen das
nicht auf Anhieb.

von Alex a. (swam)


Lesenswert?

Was mit aufgefallen ist und mich beim Versuch es zu verstehen gewundert 
hat! Hier nochmal das Beispiel aus dem Tutorial:
1
#include <avr/io.h>
2
#include <inttypes.h>
3
#ifndef F_CPU
4
#warning "F_CPU war noch nicht definiert, wird nun mit 3686400 definiert"
5
#define F_CPU 3686400UL     /* Quarz mit 3.6864 Mhz  */
6
#endif
7
#include <util/delay.h>     /* bei alter avr-libc: #include <avr/delay.h> */      
8
 
9
/* Einfache Funktion zum Entprellen eines Tasters */
10
inline uint8_t debounce(volatile uint8_t *port, uint8_t pin)
11
{
12
    if ( !(*port & (1 << pin)) )
13
    {
14
        /* Pin wurde auf Masse gezogen, 100ms warten   */
15
        _delay_ms(50);   // Maximalwert des Parameters an _delay_ms 
16
        _delay_ms(50);   // beachten, vgl. Dokumentation der avr-libc
17
        if ( *port & (1 << pin) )
18
        {
19
            /* Anwender Zeit zum Loslassen des Tasters geben */
20
            _delay_ms(50);
21
            _delay_ms(50); 
22
            return 1;
23
        }
24
    }
25
    return 0;
26
}
27
 
28
int main(void)
29
{
30
    DDRB &= ~( 1 << PB0 );        /* PIN PB0 auf Eingang Taster)  */
31
    PORTB |= ( 1 << PB0 );        /* Pullup-Widerstand aktivieren */
32
    ...
33
    if (debounce(&PINB, PB0))
34
    {
35
        /* Falls Taster an PIN PB0 gedrueckt     */
36
        /* LED an Port PD7 an- bzw. ausschalten: */
37
        PORTD = PORTD ^ ( 1 << PD7 );
38
    }
39
    ...
40
}

Bei Betrachtung des Tutorialbeispiels sind die Parameter der Funktion 
"debounce()" PORT-Zeiger und PIN-Kopie.
1
inline uint8_t debounce(volatile uint8_t *port, uint8_t pin)

Bei dem Einsatz in der Schleife werden die Parameter aber genau 
andersherum übergeben.
1
 if (debounce(&PINB, PB0))

Muss innherhalb der debounce-Funktion nicht alles genau andersherum 
definiert sein, würde heißen: pin ist gleich port und port ist gleich 
pin. Die Bedingungen verändern sich zwar nicht, aber mir würde es 
logischer erscheinen.

Berichtigt mich!;)

von Tom (Gast)


Lesenswert?

Für vom Menschen betätigte Hardwareschalter ist das Beste im 
Timer-Interrupt alle x ms zu pollen, dann hat man automatisch eine 
Entprellung und das Problem ist aus Softwaresicht sauber gelöst.

Wie ich sehe wird diese Variante sogar im verlinkten Artikel 
vorgeschlagen.



Gruß Tom

P.S. Die Sleep/Delay-Variante ist für einen kleinen Test, wie in deinem 
Fall, schon die richtige Variante (vergleiche mal die nötigen Codezeilen 
der Lösungen), aber wenn dein Programm irgendwann mal noch mehr machen 
soll, dann willst du nicht überall Verzögerungen drin haben in denen 
dein Programm nichts sinnvolles machen kann.

von Peter D. (peda)


Lesenswert?

Sie ist schon syntaktisch richtig (pin = Bitnummer 0..7),
aber sie funktioniert nicht.


Peter

von Alex a. (swam)


Lesenswert?

Tom schrieb:
> Für vom Menschen betätigte Hardwareschalter ist das Beste im
> Timer-Interrupt alle x ms zu pollen, dann hat man automatisch eine
> Entprellung und das Problem ist aus Softwaresicht sauber gelöst.
>
> Wie ich sehe wird diese Variante sogar im verlinkten Artikel
> vorgeschlagen.
>
>
>
> Gruß Tom
>
> P.S. Die Sleep/Delay-Variante ist für einen kleinen Test, wie in deinem
> Fall, schon die richtige Variante (vergleiche mal die nötigen Codezeilen
> der Lösungen), aber wenn dein Programm irgendwann mal noch mehr machen
> soll, dann willst du nicht überall Verzögerungen drin haben in denen
> dein Programm nichts sinnvolles machen kann.

Ja, alles klar! Aber die Nachteile dieser Lösung kann ich ja nur 
verstehen, wenn ich die Nachteile dann auch verstehe ;)
Ich will innerhalb meines ersten kleinen Projektes nur einen 
Programmablauf ein- und ausschalten kÖnnen. Das sollte da reichen!

Mir gings hier nur um die Richtigkeit und Verständlichkeit des Tutorials 
für noobs, wie mich. Was innerhalb der debouncefunktion erwartet wird 
und letzlich übergeben wird, würde für mich bei umgekehrter 
Parameterbenennung (*port->*pin,pin->port) einfach mehr sinn machen. So 
funktionierts ebenfalls und sieht auch noch gut aus... wär das falsch?

von Achim S. (achims)


Lesenswert?

Hallo Peter
bin auch gerade dabei deinen Code zu verstehen und zu nutzen. Habe dabei 
ein Problem. Ich habe in meinem Prg ein Timer mit 10ms drin, denn ich 
auch für andere Sachen nutze. In deinem Prg ist auch einer drin. Wie 
kann ich meinen jetzt verwenden und wo ist der Punkt zum Einsprung?
achim

von Karl H. (kbuchegg)


Lesenswert?

Alex alex schrieb:


> Muss innherhalb der debounce-Funktion nicht alles genau andersherum
> definiert sein, würde heißen: pin ist gleich port und port ist gleich
> pin. Die Bedingungen verändern sich zwar nicht, aber mir würde es
> logischer erscheinen.
>
> Berichtigt mich!;)

Das passt schon.

Aus Sicht der Funktion wird die Bezeichnung 'port' für den Anschluss 
benutzt, während die Bezeichnung 'pin' die Bitnummer an diesem Anschluss 
darstellt.

Am Port B gibt es aber 3 Register, die diesem Anschluss zugeordnet sind, 
je nachdem welche Funktionalität man von einem Bit an diesem Anschluss 
haben will.

Port B      PORTB      das Ausgangsregister
            PINB       das Eingangsregister
            DDRB       das Konfigurationsregister.


Hier ist PINB auch nichts anderes als ein Port. Nämlich der Teil des 
Port B an dem externe Signale reinkommen.

Lass dich nicht von den Bezeichnungen täuschen. Das PINB ist 
konzeptionell etwas völlig anderes, als der 'pin' in der Funktion.

  PINB   <==>   ein kompletter Anschluss (ein Port) mit 8 Bit
  pin    <==>   die Nummer eines Bits


Ausserdem ist die Funktion bescheuert. Die C-Tuorial Macher sollten sich 
überlegen, diesen Quatsch aus dem Tut rauszunehmen.

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:
> Hallo Peter
> bin auch gerade dabei deinen Code zu verstehen und zu nutzen. Habe dabei
> ein Problem. Ich habe in meinem Prg ein Timer mit 10ms drin, denn ich
> auch für andere Sachen nutze. In deinem Prg ist auch einer drin. Wie
> kann ich meinen jetzt verwenden und wo ist der Punkt zum Einsprung?
> achim

Übernimm den ISR Teil in deine 10ms ISR. Den Teil mit der Neuberechnung 
des Timer Counters lässt du einfach weg.

von Peter D. (peda)


Lesenswert?

Alex alex schrieb:
> Ich will innerhalb meines ersten kleinen Projektes nur einen
> Programmablauf ein- und ausschalten kÖnnen. Das sollte da reichen!

Nur wenn Dein Programm fast nichts macht.
Die Routine lebt davon, daß die CPU zu 99% darin verweilt.
Dauert Deine Funktion aber selber schon 100ms, bist Du nur zu 50% darin, 
d.h. im Mittel kriegst Du nur jeden 2. Tastendruck erkannt.
Und solange Du drückst, läuft Dein Programm auch nur halb so schnell.
Daher ist sie in der Praxis unbrauchbar.


Alex alex schrieb:
> würde für mich bei umgekehrter
> Parameterbenennung (*port->*pin,pin->port) einfach mehr sinn machen.

Nö.
Ein Port hat immer die Breite von einem Byte (8Bit) bzw. bei größeren 
CPUs auch 16 oder 32Bit.
Ein Pin ist aber nur ein einzelner Pin (1Bit).
Um nun aus dem Byte genau einen Pin auszuwählen braucht man eben die 
Pinnummer.
(pin -> pin-number).


Peter

von Alex a. (swam)


Lesenswert?

Karl Heinz Buchegger schrieb:

> Lass dich nicht von den Bezeichnungen täuschen. Das PINB ist
> konzeptionell etwas völlig anderes, als der 'pin' in der Funktion.
>
>   PINB   <==>   ein kompletter Anschluss (ein Port) mit 8 Bit
>   pin    <==>   die Nummer eines Bits

Danke!

Peter Dannegger schrieb:

> Nö.
> Ein Port hat immer die Breite von einem Byte (8Bit) bzw. bei größeren
> CPUs auch 16 oder 32Bit.
> Ein Pin ist aber nur ein einzelner Pin (1Bit).
> Um nun aus dem Byte genau einen Pin auszuwählen braucht man eben die
> Pinnummer.
> (pin -> pin-number).

Das hilft mir! Aber ich benutzt jetzt ohnehin deine 
Debouncefunktion...die kommt besser ;)

von Achim S. (achims)


Lesenswert?

Hallo
Habe mit der ISR so mein Problem. Habe die ISR mal dargestellt. Leider 
ist der Code zu komplieziert für mich. Würde ihn sehr gerne nutzen und 
ein bisschen verstehen.
ISR( TIMER0_OVF_vect )                      // every 10ms
{
  static uint8_t ct0, ct1, rpt;
  uint8_t i;

  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5); // preload 
10ms

  i = key_state ^ ~KEY_PIN;                 // key changed ?
  ct0 = ~( ct0 & i );                       // reset or count ct0
  ct1 = ct0 ^ (ct1 & i);                    // reset or count ct1
  i &= ct0 & ct1;                           // count until roll over ?
  key_state ^= i;                           // then toggle debounced 
state
  key_press |= key_state & i;              // 0->1: key press detect
  if( (key_state & REPEAT_MASK) == 0 )     // check repeat function
     rpt = REPEAT_START;                   // start delay
  if( --rpt == 0 ){
    rpt = REPEAT_NEXT;                                // repeat delay
    key_rpt |= key_state & REPEAT_MASK;
  }
}
Was brauch ich den eigentlich?
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:
> Hallo
> Habe mit der ISR so mein Problem. Habe die ISR mal dargestellt. Leider
> ist der Code zu komplieziert für mich. Würde ihn sehr gerne nutzen und
> ein bisschen verstehen.

Kann ich verstehen.
Allerdings. Der Code ist tricky. Ausnahmsweise gehe ich bei diesem Code 
von meiner generellen Empfehlung ab, das man Code den man übernimmt auch 
verstehen muss.
Ich denke, ich bin nicht schlecht in der Analyse von Code, na ja nach 
nahezu 30 Jahren sollte ich das auch können. Aber auch ich hab bei 
diesem Code mehr als 45 Minuten gebraucht um rauszuknobeln wie und warum 
das funktioniert.



> Was brauch ich den eigentlich?


BIs auf die Zeile

>   TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5); // preload
> 10ms

alles.

Das ist der Teil, der in die ISR kommt. Die ISR hast du ja schon hast 
und mit 10ms passt die auch gut ins Konzept. Für den Entprellcode ist 
die genaue Einhaltung der 10ms nicht wirklich wichtig. Verwendbar sind 
an ISR Zykluszeiten alles von ca.5ms bis hinauf zu 50ms. Kleiner als 5ms 
sollte die Zykluszeit nicht werden, weil man dann schon in die 
Prellzeiten hineinkommt und eine sichere Entprellung unter allen 
Umständen nicht mehr garantiert werden kann. Länger ist zwar kein 
technisches Problem, aber dann kann es sein, das man als Benutzer 
unangenehm merkt, dass zwischen dem Drücken einer Taste und der 
Programmreaktion Zeit vergeht. Aber dazwischen ist alles was man an ISR 
hat fein und kann benutzt werden.

Also:
* den Teil in die ISR kopieren
1
  static uint8_t ct0, ct1, rpt;
2
  uint8_t i;
3
  
4
  i = key_state ^ ~KEY_PIN;                       // key changed ?
5
  ct0 = ~( ct0 & i );                             // reset or count ct0
6
  ct1 = ct0 ^ (ct1 & i);                          // reset or count ct1
7
  i &= ct0 & ct1;                                 // count until roll over ?
8
  key_state ^= i;                                 // then toggle debounced state
9
  key_press |= key_state & i;                     // 0->1: key press detect
10
 
11
  if( (key_state & REPEAT_MASK) == 0 )            // check repeat function
12
     rpt = REPEAT_START;                          // start delay
13
  if( --rpt == 0 ){
14
    rpt = REPEAT_NEXT;                            // repeat delay
15
    key_rpt |= key_state & REPEAT_MASK;
16
  }

* Die globalen Variablen
1
volatile uint8_t key_state;                                // debounced and inverted key state:
2
                                                  // bit = 1: key pressed
3
volatile uint8_t key_press;                                // key press detect
4
 
5
volatile uint8_t key_rpt;                                  // key long press and repeat
übernimmst du so wie sie sind

* Ebenso die Funktionen
1
uint8_t get_key_press( uint8_t key_mask )
2
uint8_t get_key_rpt( uint8_t key_mask )
3
uint8_t get_key_short( uint8_t key_mask )
4
uint8_t get_key_long( uint8_t key_mask )
eventuell könnte man bei den Funktionen noch überlegen, welche Features 
man zu nutzen gedenkt
  * will ich lange und kurze Tastendrücke unterscheiden können?
    wenn nicht, brauch ich get_key_short und get_key_long nicht
  * will ich Autorepeat nutzen?
    wenn nicht, ist get_key_rpt überflüssig

* Dann werden die #define übernommen und gleich konfiguriert
  Am besten denkt man sich gleich mal ein paar sprechende Namen
  für die Tasten aus.
  Habe ich zb eine Menüsteuerung mit den Tasten 'Links' 'Rechts' und
  'Enter', dann arbeite ich das gleich in die #define mit ein.

Erst mal die generelle Definitionne für den Port, an dem die Tasten 
sitzen
1
#define KEY_DDR         DDRB
2
#define KEY_PORT        PORTB
3
#define KEY_PIN         PINB
Danach die Tasten, so wie ich sie in meiner App benötige. Da benutze ich 
gleich 'sprechende' Namen. Die Zahlen sind ganz einfach die Pinnummern 
am Port, an dem jeweils die Taste angeschlossen ist (Die Taste 'Links' 
wäre also am Pin 0 vom Port B)
1
#define KEY_LEFT        0
2
#define KEY_RIGHT       1
3
#define KEY_ENTER       2

In ALL_KEYS werden einfach alle definierten Tasten in einer einzigen 
Maske zusammengefasst
1
#define ALL_KEYS        (1<<KEY_LEFT | 1<<KEY_RIGHT | 1<<KEY_ENTER)

Fehlt noch die Festlegung für den Autorepeat. Welche Tasten sollen 
grundsätzlich mit einem Autorepeat belegbar sein und wie soll das 
Zeitverhalten für den Autorepeat sein. Lässt du die Zeiten-Einstellungen 
so wie im Artikel angegeben, dann ergibt sich eine brauchbare 
Defaulteinstellung. Da muss also erst mal nichts angepasst werden, es 
sei denn, es zeigt sich beim Testen, dass das etwas zu schnell/langsam 
geht. Das kann zb sein, weil die ISR eben nicht 10ms hat sondern 
weniger/mehr.
In meinem Beispiel mit der Menüsteuerung möchte ich zb auf Links/Rechts 
einen Autorepeat machen können, aber nicht auf Enter. Also werden auch 
nur die Tasten Links/Rechts aufgeführt. Achtung: Das heißt nicht, dass 
da jetzt schon ein Autorepeat drauf liegt. Das heißt nur, dass die 
Möglichkeit dazu besteht.
Also
1
#define REPEAT_MASK     (1<<KEY_LEFT | 1<<KEY_RIGHT)
2
#define REPEAT_START    50                        // after 500ms
3
#define REPEAT_NEXT     20                        // every 200ms

Fehlt nur noch der Teil, der ins Hauptprogramm muss.
Da du ja bereits einen Timer bei dir laufen hast, interessiert der dich 
nicht weiter, du brauchst nur die Portinitialisierung des Tastenports
1
...
2
int main()
3
{
4
  ...
5
6
  KEY_DDR &= ~ALL_KEYS;                // configure key port for input
7
  KEY_PORT |= ALL_KEYS;                // and turn on pull up resistors
8
  ...
9
10
  sei();
11
  while( 1 ) {
12
    ...
13
  }
14
}

Tja. Das wars auch schon. Ab jetzt kannst du mit den Tastenabfragen 
deine Tasten mit Leben befüllen
1
int main()
2
{
3
  ...
4
5
  while( 1 ) {
6
7
    if( get_key_press( 1 << KEY_LEFT ) )
8
      mach irgendwas, zb eine LED einschalten
9
10
    if( get_key_press( 1 << KEY_RIGHT ) )
11
      mach was anderes, zb die LED wieder abschalten
12
  }
13
}

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
entschuldige, ich bin platt. Mir fehlen im Moment die Worte,um es 
auszudrücken. Hatte eigentlich schon viel erwartet, aber bin einfach 
platt. Werde Wochen und Monate brauchen, um damit klar zu kommen. Kann 
nur meinen Hut ziehen und dir vielmals danken. Werde es ausführlich 
durcharbeiten und darüber berichte wie ich es einsetze. Muss dir noch 
aus einem anderen Grund danken. Du hast mir schon wiederholte male 
geholfen. Denke dabei an den Timer und die LED und entschuldige meine 
blöden Fragen.
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:
> Hallo Karl Heinz
> entschuldige, ich bin platt. Mir fehlen im Moment die Worte,um es
> auszudrücken. Hatte eigentlich schon viel erwartet, aber bin einfach
> platt. Werde Wochen und Monate brauchen, um damit klar zu kommen.


Ah geh.
Da bei dir der Timer schon läuft, ist das eine Sache auf 10 Minuten und 
du hast deine Tasten am laufen.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
Für dich 10 Minuten, eine Traumzahl für mich. Es geht mir immer darum. 
es zu verstehen. Mein Ziel ist es, dich nicht mehr mit Anfänger Fragen 
zu löchern. Danke
achim

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
10 min sind eine Traumzahl. Habe jetzt rund 2 h gebraucht. Mit Kontrolle 
für vielen kleinen Zeichen. Zum Schluss ist aber doch noch eine 
Fehlermeldung gekommen. Habe sie mal kopiert:

..default/../Arbeit_Timer.c:117: undefined reference to `get_key_press'
..default/../Arbeit_Timer.c:121: undefined reference to `get_key_press'

 if(get_key_press(1<<KEY_LEFT)){
     // zB LED an
   }
 if(get_key_press(1<<KEY_RIGHT)){
    // zB LED aus
   }
Das sind die beiden Zeilen, in dem der Fehler stecken soll.

volatile uint8_t key_state;
volatile uint8_t key_press;
volatile uint8_t key_rpt;
 uint8_t get_key_press(uint8_t key_mask);
 uint8_t get_key_rpt(uint8_t key_mask);
 uint8_t get_key_short(uint8_t key_mask);
 uint8_t get_key_long(uint8_t key_mask);

In diesem Teil wird doch die Variable deklariert. Kann im Moment keinen 
Fehler sehen.
Noch eine kleine Frage zum Timer. Habe den Text eingetragen, so wie due 
es geschrieben hast. die eine Zeile ist raus. Ich hatte vorher wait10 
als 10ms Zeit für mich. Welcher Teil ist den 10ms?
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:
> Hallo Karl Heinz
> 10 min sind eine Traumzahl. Habe jetzt rund 2 h gebraucht. Mit Kontrolle
> für vielen kleinen Zeichen. Zum Schluss ist aber doch noch eine
> Fehlermeldung gekommen. Habe sie mal kopiert:
>
> ..default/../Arbeit_Timer.c:117: undefined reference to `get_key_press'
> ..default/../Arbeit_Timer.c:121: undefined reference to `get_key_press'

Du musst dir schon die komplette Funktion von dem Link in deinen Code 
einkopieren!


>  uint8_t get_key_press(uint8_t key_mask);

Das ist ja noch nicht die Funktion. Da fehlt ja die Implementierung

Aus dem Link
1
///////////////////////////////////////////////////////////////////
2
//
3
// check if a key has been pressed. Each pressed key is reported
4
// only once
5
//
6
uint8_t get_key_press( uint8_t key_mask )
7
{
8
  cli();                                          // read and clear atomic !
9
  key_mask &= key_press;                          // read key(s)
10
  key_press ^= key_mask;                          // clear key(s)
11
  sei();
12
  return key_mask;
13
}

Ich sah einfach nur keinen Sinn darin, in meiner Checkliste die 
Funktionen komplett einzukopieren. Deshalb hab ich ja auch keinen ; ans 
Zeilenende gesetzt.
Ich hatte eigentlich schon gedacht, dass du eine Funktion erkennst, wenn 
ich nur den Funktionskopf anführe.


>  uint8_t get_key_rpt(uint8_t key_mask);
>  uint8_t get_key_short(uint8_t key_mask);
>  uint8_t get_key_long(uint8_t key_mask);
>
> In diesem Teil wird doch die Variable deklariert.

Ah geh.
Das sind doch keine Variablen. Seit wann wird denn bei einer 
Variablendefinition ein ( ) Teil geschrieben.

> Noch eine kleine Frage zum Timer. Habe den Text eingetragen, so wie due
> es geschrieben hast. die eine Zeile ist raus. Ich hatte vorher wait10
> als 10ms Zeit für mich.

Ähm. Du hattest was?

Hast du nun einen Timer am laufen oder nicht?

wait10 klingt nicht nach einem Timer. Das klingt nach einer 
Warteschleife. Und das ist unbrauchbar.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
danke für deine Antwort
Habe leider den Teil zwar gesehen, aber nicht ernst genommen. Da der 
Code sehr tricky ist, habe ich die Funktion noch nicht verstanden. Da 
her auch übersehen. Habe einige Teile übersetzt in mein Verständnis, 
fehlt aber noch einen ganze Menge. Zur Kontrolle vergleiche ich immer 
mit dem ganzen Code und dabei die Stelle im Ablauf. Habe einen Timer am 
laufen.

void nibo_timer2()                          // Timer 10ms
  {    TCNT2 = 0;
    OCR2=249;
    TCCR2=(1<<WGM21)|(1<<CS21)|(1<<CS20);
    TIMSK |= (1<<OCIE2);  }

ISR (TIMER2_COMP_vect)                      // wait1=1ms,
  {
    if(  wait<=9)                           // Takt 1ms, bei 9 sind es 
10ms
      {  wait++;    }                       // erhöht
    else                                    // wenn dann ...
      {  wait=0;                            // setzt wait auf 0
         wait10=0xFF;                       // Signal alle 10ms wait10
      }
  }

Ich habe ihn mit der ganzen ISR kopiert. Im Moment nutze ich ihn für den 
Aufbau einer Warteschleife. Zur Kontrolle schalte ich zur Zeit nur LED. 
Daher auch der Name.
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:
> Hallo Karl Heinz
> danke für deine Antwort
> Habe leider den Teil zwar gesehen, aber nicht ernst genommen. Da der
> Code sehr tricky ist, habe ich die Funktion noch nicht verstanden.

Entdschuldige, aber das ist jetzt eine faule Ausrede.

Der ISR-Teil ist von der Funktion her tricky. Der Rest ist klar wie 
Klossbrühe. Insbesondere wenn es nur darum geht: was brauch ich und was 
nicht.
Da werden Funktionen aufgerufen, also muss es die Funktionen auch 
irgendwo geben.

> mit dem ganzen Code und dabei die Stelle im Ablauf. Habe einen Timer am
> laufen.

Na dann passt es doch.
Den ISR-Code mit in die ISR aufnehmen und gut ists.

>
> void nibo_timer2()                          // Timer 10ms
>   {    TCNT2 = 0;
>     OCR2=249;
>     TCCR2=(1<<WGM21)|(1<<CS21)|(1<<CS20);
>     TIMSK |= (1<<OCIE2);  }
>
> ISR (TIMER2_COMP_vect)                      // wait1=1ms,
>   {
>     if(  wait<=9)                           // Takt 1ms, bei 9 sind es
> 10ms
>       {  wait++;    }                       // erhöht
>     else                                    // wenn dann ...
>       {  wait=0;                            // setzt wait auf 0
>          wait10=0xFF;                       // Signal alle 10ms wait10
>       }
>   }
>
> Ich habe ihn mit der ganzen ISR kopiert. Im Moment nutze ich ihn für den
> Aufbau einer Warteschleife. Zur Kontrolle schalte ich zur Zeit nur LED.
> Daher auch der Name.

Ja, ok.
Und jetzt kopierst du da noch den ISR Code mit rein, so dass er alle 
10ms ausgeführt wird. Bei dir ist es der else Teil, der alle 10ms 
ausgeführt wird. Also kommt der Code in diesen Zweig rein.
Wo ist das Problem?
1
ISR (TIMER2_COMP_vect)                      // wait1=1ms,
2
{
3
  static uint8_t ct0, ct1, rpt;
4
  uint8_t i;
5
6
  if(  wait<=9)                           // Takt 1ms, bei 9 sind es 10ms
7
  {
8
    wait++;
9
  }                       // erhöht
10
  else                                    // wenn dann ...
11
  {
12
    wait=0;                            // setzt wait auf 0
13
    wait10=0xFF;                       // Signal alle 10ms wait10
14
15
    i = key_state ^ ~KEY_PIN;                       // key changed ?
16
    ct0 = ~( ct0 & i );                             // reset or count ct0
17
    ct1 = ct0 ^ (ct1 & i);                          // reset or count ct1
18
    i &= ct0 & ct1;                                 // count until roll over ?
19
    key_state ^= i;                                 // then toggle debounced state
20
    key_press |= key_state & i;                     // 0->1: key press detect
21
 
22
    if( (key_state & REPEAT_MASK) == 0 )            // check repeat function
23
      rpt = REPEAT_START;                          // start delay
24
    if( --rpt == 0 ){
25
      rpt = REPEAT_NEXT;                            // repeat delay
26
    key_rpt |= key_state & REPEAT_MASK;
27
  }
28
}

fertig ist der Einbau in die ISR.
Auch wenn du die Funktion des Codes nicht verstehen musst, mitdenken 
musst du schon. Das Ganze ist schon so aufbereitet, dass man es leicht 
übernehmen und einbauen kann. Dazu muss man nicht verstehen, wie der 
Code im Detail arbeitet. Aber das entbindet nicht, das Prinzip zu 
verstehen, wie die Einzelteile zusammenarbeiten. Und das ist wie immer: 
In der ISR findet die Entprellung statt und der Code hinterlässt in den 
globalen Variablen das was er rausgefunden hat, bzw. benutzt die als 
sein Zwischenspeicher. Die restlichen Funktionen holen sich die 
Ergebnisse aus diesen globalen Variablen.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

@ Achim Seeger
Ein Tipp: mach mal um deine Codeabschnitte sowas (ohne die Leerzeichen 
in den []:

[ c ]
#define dein C-Code
  static int  rpt;      // das hier ist ein unglaublich in die Breite 
gezogener Kommentar
  char i;

  if(wait<9)
    wait++;
  else
    wait=0;
[ /c ]

Und heraus kommt sowas:
1
#define dein C-Code
2
  static int  rpt;      // das hier ist ein unglaublich in die Breite gezogener Kommentar
3
  char i;
4
5
  if(wait<9)
6
    wait++;
7
  else     
8
    wait=0;

von Achim S. (achims)


Lesenswert?

Danke für deine Hilfe.
Das grösste Problem, was ich habe, sitzt wahrscheinlich vor dem Monitor!
achim

von 4q34521g (Gast)


Lesenswert?

Ist das hier alles gruselig. Ich kriege schon ne Gänsehaut.

von Tommy (Gast)


Lesenswert?

Was kann man an dem 10-Zeiler bitte nicht verstehen? Und wofür soll man 
da mehr als 2min zum Verständnis brauchen?

von Achim S. (achims)


Lesenswert?

Dann las uns doch an deiner Weisheit teil haben. Bin ganz begierig auf 
deine Erklärung zu meiner Erleuchtung.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
Habe den Code endlich Fehlerfrei abgetippt und kopieren können. Wie 
üblich liegt der Teufel im Detail. Es ist manchmal kaum sichtbar, 
welcher Punkt oder Klammer was ist. Es geht jetzt. Nach deinem Code 
liegt die Tastenerkennung in diesen Zeilen

1
#define KEY_DDR    DDRB    // Defi an welchem Port die Tasten sitzen
2
#define KEY_PORT   PORTB
3
#define KEY_PIN    PINB
4
5
#define KEY_LEFT  0        // Taste links
6
#define KEY_RIGHT  1       // Taste rechts
7
#define KEY_ENTER  2       // Taste Enter

Dabei wird der ganze Port (PORTB) verwendet. Da ich im Moment nur 1 
Taste verwende, kann ich es so nicht schreiben. Verwende jetzt beim 
Atmega 128 PD4 und später noch PD6, PD5 und PD3.
Ansonsten warte ich noch auf die Erleuchtung durch Tommy. Sorry, bin 
kein Profi. Versuche es mir doch mal zu erklären. Danke Tommy
achim

von Tommy (Gast)


Lesenswert?

Der Code ist kommentiert und ist auch nicht obfuscated, was kann ich 
hier mehr schreiben als schon im Code steht? Und es werden ja auch nur 
ganz einfache Operationen verwendet, in kurzen Anweisungen.

Am besten gehst du den Code mit für verschiedene Szenarios durch, also 
für einen Taster und dann Taste gedrückt, Taste los gelassen, Taste 
nicht gedrückt, etc.

So mache ich das auch. Mit Übung gehts natürlich entsprechend schneller.

von Achim S. (achims)


Lesenswert?

Hallo Tommy
Ich bereits einiges probiert. z.B. Eingabe der Adressen, veränderte PINS 
usw. Leider kam es nicht zu einem Ergebnis. So viel ich gesehen habe, 
geht das das über das gesamze Port mit entspr, Breite. Wenn ich nur 
einen brauche kann ich es über Bitmanipulatiin machen. Es wir aber dabei 
der gesamte Port angegeben. Das ist leider die Stelle wo ich schwimme. 
Werde es weiter testen. Es muss gehen, ist mir klar.
achim

von Karl H. (kbuchegg)


Lesenswert?

Tommy schrieb:
> Der Code ist kommentiert und ist auch nicht obfuscated, was kann ich
> hier mehr schreiben als schon im Code steht? Und es werden ja auch nur
> ganz einfache Operationen verwendet, in kurzen Anweisungen.
>


Du 'erklärst' den COde nicht. Du liest ihn nur vor. Das hat mit erklären 
nichts zu tun.

Wenn ich jemandem den Code erkläre, dann muss ich ihm die Idee 
vermitteln, die dahintersteckt. Und ich muss ihm zeigen, wie sich diese 
Idee in den Zeilen des Codes wiederfindet.

> Am besten gehst du den Code mit für verschiedene Szenarios durch, also
> für einen Taster und dann Taste gedrückt, Taste los gelassen, Taste
> nicht gedrückt, etc.

Das hilft zwar. Aber ohne die generelle Idee herausgefunden zu haben, 
wirst du nicht oder nur sehr schwer ergründen können, welche Rolle die 
Variablen ct0 und ct1 spielen. Und ein Neuling findet das erst recht 
nicht raus, dass sich in diesen 2 Variablen im Prinzip 8 Stück 2-Bit 
Zähler verbergen. Aber sag mal nach deiner 2 Minuten Analyse: zählen die 
Zähler rauf oder runter?

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:

>
1
> #define KEY_DDR    DDRB    // Defi an welchem Port die Tasten sitzen
2
> #define KEY_PORT   PORTB
3
> #define KEY_PIN    PINB
4
> 
5
> #define KEY_LEFT  0        // Taste links
6
> #define KEY_RIGHT  1       // Taste rechts
7
> #define KEY_ENTER  2       // Taste Enter
8
>
>
> Dabei wird der ganze Port (PORTB) verwendet. Da ich im Moment nur 1
> Taste verwende, kann ich es so nicht schreiben. Verwende jetzt beim
> Atmega 128 PD4 und später noch PD6, PD5 und PD3.

Dann konfigurierst du das eben nach deinen Bedürfnissen.

Deine Tasten liegen am Port D. Also
1
#define KEY_DDR    DDRD    // Defi an welchem Port die Tasten sitzen
2
#define KEY_PORT   PORTD
3
#define KEY_PIN    PIND

und dort liegt die Taste am Pin 4. Also
1
#define KEY_1    4

es schadet auch nichts, wenn du gleich mal alle Tasten angibst. Solange 
die Hardwaremässig nicht vorhanden sind, wertest du sie halt einfach 
nicht aus

Also
1
#define KEY_1    4 
2
#define KEY_2    3
3
#define KEY_3    5
4
#define KEY_4    6

Ich versteh jetzt ehrlich gesagt nicht, was (überhaupt nachdem ich dich 
weiter oben durch den Prozess geführt habe) daran jetzt besonders schwer 
sein soll.

Jeder der schon mal ein Blinkprogramm an seine speziellen Verhältnisse 
angepasst hat, an dem die LED eben an einem anderen Pin sitzt als in der 
Vorlage, muss doch erkennen und wissen, was es mit den 'Wörtern' DDRB, 
PORTB und PINB auf sich hat, und dass er die eben gegen die 'Wörter' 
austauscht, die in seiner speziellen Hardwaresituation vorliegen.

> Ansonsten warte ich noch auf die Erleuchtung durch Tommy.

Auf die warte ich auch. Die 2 Minuten sind schon lange um.

von Uwe S. (de0508)


Lesenswert?

Guten Morgen,

ich hatte mal ein lauffähiges Programm mit Peters Routinen 
veröffentlicht.

Vielleicht hilft es bel Querlesen und Verstehen.

Beitrag "Re: 3fach Timer (einschaltverzögerung)"

siehe c-src/tools/..

von Peter D. (peda)


Lesenswert?

Achim Seeger schrieb:
> So viel ich gesehen habe,
> geht das das über das gesamze Port mit entspr, Breite.

Genau das ist der Trick. Um den Code zu verstehen, reicht es, nur ein 
Bit (Taste) je Variable zu betrachten, z.B. Bit 0.
Und wenn man dann die Funktion verstanden hat, ist es für Bit 1 .. 7 
genau gleich. Die Logikoperatoren verknüpfen ja immer nur Bits an 
gleicher Stelle, die Nachbarbits beeinflussen sie nicht.


Achim Seeger schrieb:
> Wenn ich nur
> einen brauche kann ich es über Bitmanipulatiin machen.

Genau das macht der Code. Für eine Taste könnte man es auch anders lösen 
bei fast gleichem Aufwand.
Aber schon bei 2 Tasten ist der Aufwand anderer Lösungen doppelt so hoch 
und bei 8 Tasten sogar 8-fach.


Peter

von Achim S. (achims)


Lesenswert?

Hallo Peter
eigentlich hast du mein Hauptproblem gefunden. Ich arbeite in der 
Grundversion nur mit einer Taste. Wenn ich mir den Code anshe, so ist er 
riesengross. Anders betrachtet, wenn ich mit einem Timer arbeite und für 
jede Funktion nur so viel Zeit nehme wie es unbedingt notwendig ist, 
habe ich mit den "normalen" Abfragen ein Problem. Es geht nicht so. 
Damit sind wir wieder bei deinem Taster. Habe das mit dem Verstehen erst 
mal vertagt. Wenn ich mehr in C stecke, sieht es anders aus. Wie kann 
ich es vernünftig gestalten, erst mal ein PIN abzufragen mit der Option 
auf weitere PINs. Dabei ist die Sache mit den Botschieben an dieser 
Stelle unklar. Du verwendest DDR, PORT, PIN. Bei einem PIN klar. Wie ist 
es mit unterschiedlichen?
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:
> Hallo Peter
> eigentlich hast du mein Hauptproblem gefunden. Ich arbeite in der
> Grundversion nur mit einer Taste. Wenn ich mir den Code anshe, so ist er
> riesengross.

Nicht wirklich.
Der Teil in der ISR übersetzt sich zu einer Handvoll Assembler 
Anweisungen. Das ist nichts was dich beunruhigen müsste. Lass dich nicht 
vom C-Code ins Boxhorn jagen. Entscheidend ist, was der Compiler in der 
Maschinensprachenversion daraus macht. Und da bleibt nicht viel übrig.

> Anders betrachtet, wenn ich mit einem Timer arbeite und für
> jede Funktion nur so viel Zeit nehme wie es unbedingt notwendig ist,
> habe ich mit den "normalen" Abfragen ein Problem.

Wetten nicht.
Wenn dein Code so zeitkritisch ist, dass dir 10 oder 15 Taktzyklen (bei 
1Mhz Taktfrequenz sind das ~15 MYKRO-Sekunden) in einer Timer-ISR, die 
alle 10 Millisekunden aufgerufen wird, tatsächlich weh tun, dann stimmt 
schon an ganz anderer Stelle etwas nicht.
(ich geh jetzt mal davon aus, dass du NICHT ein Videosignal erzeugst, 
bei dem tatsächlich jede µs wichtig ist. Oder irgendetwas anderes, bei 
dem ähnliche enge zeitliche Vorgaben unabdingbar sind)


Dieser Code wird von tausenden Programmierern problemlos eingesetzt, im 
Moment sehe ich keinen wirklichen Grund, warum das bei dir nicht auch 
klappen sollte.
Und um ehrlich zu sein: Nachdem was ich in anderen Threads von dir so 
gesehen habe, bezweifle ich ganz massiv, dass du die Situation überhaupt 
korrekt und vernünftig einschätzen kannst.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
du hast recht. Der Code geht. Habe ihn ja nach einiger Zeit so weit zum 
laufen bekommen. Eines der Problem ist dabei sicher die Anpassung an den 
128 und dem bisher verwendeten Code. Für mich ist der Code schon bei 
dieser Länge unübersichtlich. Daher versuche ich viel in einzelne Teile 
zu zerlegen. Dabei kommt auch der Gedanke der weiteren Verwendung. Habe 
festgestellt, das manchmal nur Kleinigkeiten angepasst werden müssen. 
Das gefällt mir. Nach dem Artikel"Warten verboten" kam ja erst der 
Gedanke mit dem Timer und die entsprechende Verarbeitung und Ablauf. Da 
ich delay nicht verwende soll alles schneller gehen. Eigentlich fangen 
dort andere Probleme an, die ich vorher so nicht bedacht habe. Damit 
sind wir wieder bei den Tasten. Ein übersetzen der bisherigen Sachen war 
kaum möglich. Dazu kommt die Auswertung der Zeiten. Da nur eine Taste 
vorhanden ist, muss man sich was ausdenken. Da kommt die Sache von Peter 
genau richtig. Sind wir wieder bei der Anpassung.

Hallo Uwe
habe mir die Sachen geholt und bin beim lesen. Den Grundartikel kenne 
ich. Dort sind einiges Datein leider nicht zu laden.
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:

> Gedanke mit dem Timer und die entsprechende Verarbeitung und Ablauf. Da
> ich delay nicht verwende soll alles schneller gehen.

Noch mal
'schnell' ist in einer ISR ein relativer Begriff. 'schnell' bedeutet 
nicht, dass man in einer ISR gar nichts tun darf. Man darf nur nicht 
exzessiv Zeit verbrutzeln.
Dieser Code ist von 'exzessiv Zeit verbrutzeln' noch weit, weit, weit 
entfernt.

> Eigentlich fangen
> dort andere Probleme an, die ich vorher so nicht bedacht habe. Damit
> sind wir wieder bei den Tasten. Ein übersetzen der bisherigen Sachen war
> kaum möglich. Dazu kommt die Auswertung der Zeiten. Da nur eine Taste
> vorhanden ist, muss man sich was ausdenken. Da kommt die Sache von Peter
> genau richtig. Sind wir wieder bei der Anpassung.

Bei allem nötigen Respekt.
Aber wenn dir diese Anpassung (bei der es nur darum geht ein paar 
#define auf die richtigen Werte - ja nach Port an dem die Tasten 
angeschlossen sind - zu stellen, insbesondere am Code selber musst du 
nichts machen, du musst nicht verstehen wie er arbeitet, du musst ihn 
nur einbinden und nach Bedarf eine Funktion aufrufen) schon 
Schwierigkeiten macht, dann solltest du dir mal überlegen, ob du nicht 
das falsche Hobby hast, bzw. ob dir da nicht viele Grundlagen fehlen.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
es fehlen Grundlagen, geb ich ehrlich zu. Mein bester Freund ist auch 
das C Buch. Bin am lernen und versuche es zu begreiffen. Ist nicht 
leicht. Einfache Sachen klappen schon ganz gut. Bei anderen muss ich 
fragen. Bei solchen Sachen wie Tasten ergebe ich mich beim Code 
verstehen. Ist auch keine Schade, zu sagen das es andere weit aus besser 
können.
Falsches Hobby nicht gerade, vielleicht zu wenig Zeit und Ausdauer.
achim

von Christian G. (christian_g83)


Lesenswert?

Achim Seeger schrieb:

> Falsches Hobby nicht gerade, vielleicht zu wenig Zeit und Ausdauer.

Ich laufe auch gerne Marathon. Habe nur zuwenig Zeit und Ausdauer. :)

von Achim S. (achims)


Lesenswert?

Alles zurück. Habe einen Text übersehen von KH. Glaube diese Stelle hat 
im Kopf gefehlt.
achim

von Tommy T. (tommy2000)


Lesenswert?

@Achim, check mal deine Mailbox.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
habe das Wochenende damit verbracht alles über DDR, PORT und PIN zu 
lesen, was ich im Netz gefunden habe. Sehe soweit durch, das ich den 
Eingang und die Zuordnung der entsprechen PIN und PORTs jetzt richtig 
begriffen habe. Nach dem ich die enrsprechenden Stelen alle geändert 
hatte, ging es trotzdem nicht. Nach 2 weiteren Stunden hatte ich dann 
die Fehler gefunden und jetzt geht es sehr gut. Möchte aber noch zum 
verständnis was fragen. Es wird
1
.. get_key_short(uint8_t key_mask)
2
.. get_key_press(~key_state & key_mask);
3
.. get_key_long(uint8_t key_mask)

verwendet. (short-kurz, press-? , long-lang) Ist damit die Länge des 
Tastendrucks gemeint und wird diese über
1
#define REPEAT_START 50      // after 500ms
2
#define REPEAT_NEXT 20      // every 200ms

eingestellt?
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:
> Hallo Karl Heinz
> habe das Wochenende damit verbracht alles über DDR, PORT und PIN zu
> lesen, was ich im Netz gefunden habe.

Ich will ja nich lästig sein, aber das sind eigentlich Dinge, die weit 
vor einer Tastenentprellung kommen oder vor der Verwendung von Timern. 
Du tust dir keinen Gefallen damit, wenn du die Basisdinge ignorierst 
oder als so trivial ansiehst, dass du keine Übungen dazu machen musst.

Nicht umsonst ist die allererste Übung, die man überhaupt macht einfach 
ein paar LED an einen Port anschliessen und dann mit den DDR Registern 
bzw. den PORT Registern da erst mal ein paar LED zum leuchten zu 
bringen, Muster zu erzeugen etc. Dann kommt ein Taster dazu, wobei es 
nicht um Entprellung etc. geht, sondern einfach nur um 'Ist der Taster 
gedrückt oder nicht'. Und so geht es weiter, Schritt um Schritt, bis man 
dann nach einiger Zeit bei der Flankenanbfrage von Tastern angelangt 
ist, plus zugehöriger Entprellung.

Überspringst du diese Schritte, dann ist klar, dass du an allen Ecken 
und Enden Wissenslücken hast. Wie gesagt, du tust dir damit selbst 
keinen Gefallen, wenn du diese Schritte überspringst.



>
1
> .. get_key_short(uint8_t key_mask)
2
> .. get_key_long(uint8_t key_mask)
3
>
>
> verwendet. (short-kurz, press-? , long-lang) Ist damit die Länge des
> Tastendrucks gemeint und wird diese über

Ja.

get_key_press und get_key_rpt gehören zusammen und können mitsammen 
benutzt werden.
und get_key_short und get_key_long gehören zusammen und können mitsammen 
benutzt werden.

Sozusagen 2 Funktionsfamilien, die jeweils miteinander können. 
Allerdings kann man die Familien miteinander nicht mischen.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
habe das Prg fertig. Es läuft so wie ich es mir vorstelle. Bitte 
beachten, es ist für einen Nibo 2 mit Atmega 128 bei 16MHz geschrieben. 
Habe viel dabei gelernt. Danke dir für deine Hilfe und Geduld.
1
/* Test Timer, Testet die Funktion Timer, ist eingestellt auf 10ms und wird 
2
vom Timer gesteuert. Tasten sind nach Peter Dannegger entprellt, bis 4 Tasten
3
, S3 kurz - an, lang - aus, by h.j.seeger@web.de  */
4
5
#include <nibo/niboconfig.h>         
6
#include <nibo/iodefs.h>
7
#include <nibo/leds.h>               
8
#include <avr/interrupt.h>
9
#include <avr/io.h>        
10
#include <nibo/gfx.h>
11
#include <nibo/display.h>
12
#include <nibo/pwm.h>
13
#include <nibo/bot.h>
14
#include <nibo/delay.h>
15
#include <stdint.h>
16
17
#define KEY_DDR    DDRD              // Datenrichtungsregister
18
#define KEY_PORT   PORTD            // Datenregister
19
#define KEY_PIN    PIND                // PIN D
20
#define KEY_1     4              // 
21
#define KEY_2      3                // 
22
#define KEY_3     5              // 
23
#define KEY_4     6                // 
24
#define ALL_KEYS (1<<KEY_1|1<<KEY_2|1<<KEY_3|1<<KEY_4)
25
#define REPEAT_MASK (1<<KEY_1|1<<KEY_2)
26
#define REPEAT_START 50              // after 500ms
27
#define REPEAT_NEXT 20              // every 200ms      
28
29
uint8_t key_state;
30
uint8_t key_press;
31
uint8_t key_rpt;
32
33
uint8_t wait10 = 0;                // ISR 10ms
34
uint8_t wait10_merker = 0;            // ISR 10ms
35
uint8_t wait = 0;                // Timer 1ms
36
uint8_t led1 = 0;                // Variable für LED 1
37
uint8_t led2 = 0;                // Variable für LED 2
38
uint8_t led3 = 0;                // Variable für LED 3
39
uint8_t led4 = 0;                // Variable für LED 4
40
uint8_t led5 = 0;                // Variable für LED 5
41
uint8_t led6 = 0;                // Variable für LED 6
42
uint8_t wait_led = 0;
43
uint8_t tast;
44
45
ISR (TIMER2_COMP_vect)              // wait1=1ms,
46
  {
47
    static uint8_t ct0,ct1,rpt;
48
    uint8_t i;
49
    if(  wait<=9)                // Takt 0,5s, bei 9 sind es 10ms
50
      {  
51
        wait++;  
52
      }                      // erhöht
53
  else                       // wenn dann ...
54
    { 
55
      wait=0;                  // setzt wait auf 0
56
      wait10=0xFF;                 // Signal alle 10ms wait10     
57
      
58
  i=key_state ^~KEY_PIN;
59
  ct0=~(ct0&i);
60
  ct1=ct0^(ct1&i);
61
  i&=ct0&ct1;
62
  key_state^=i;
63
  key_press|=key_state&i;
64
  if((key_state & REPEAT_MASK)==0)
65
  rpt=REPEAT_START;
66
  if(--rpt==0){
67
  rpt=REPEAT_NEXT;
68
  key_rpt|=key_state & REPEAT_MASK;
69
  }
70
 }}
71
72
uint8_t get_key_press(uint8_t key_mask)
73
 {
74
    cli();
75
  key_mask &=key_press;
76
  key_press^=key_mask;
77
  sei();
78
  return key_mask;
79
  }
80
81
uint8_t get_key_rpt(uint8_t key_mask)
82
  {
83
    cli();
84
    key_mask &=key_rpt;
85
  key_rpt^=key_mask;
86
  sei();
87
  return key_mask;
88
  }
89
90
uint8_t get_key_short(uint8_t key_mask)
91
  {
92
    cli();
93
    return get_key_press(~key_state & key_mask);
94
  }
95
96
uint8_t get_key_long(uint8_t key_mask)
97
  {
98
    return get_key_press(get_key_rpt(key_mask));
99
  }
100
101
void nibo_timer2()                // Timer 10ms 
102
  {  
103
    TCNT2 = 0;
104
  OCR2=249;
105
  TCCR2=(1<<WGM21)|(1<<CS21)|(1<<CS20);  
106
  TIMSK |= (1<<OCIE2);  
107
  }
108
109
int main(){                    // Start            
110
111
  sei();                    // Freigabe Interrups
112
  display_init();
113
  pwm_init();            
114
  gfx_init();            
115
  bot_init();            
116
  leds_init();                          
117
  leds_set_displaylight(800);          // setzt Displaylicht        
118
  nibo_timer2();                // Aufruf Timer 2 einmalig
119
120
KEY_DDR&=~ALL_KEYS;
121
KEY_PORT|=ALL_KEYS;
122
123
  gfx_fill(0x00);                // löscht Schirm        
124
  gfx_move(5, 5);                // Angabe Ort        
125
  gfx_set_proportional(0);          // Angabe Schrift
126
  gfx_print_text("Testprogramm Nibo 2");    // Ausgabe Text  
127
  gfx_move(8, 15);              // Angabe Ort
128
  gfx_set_proportional(1);          // Angabe Schrift
129
  gfx_print_text("Test Timer LED HJS 2012");  // Ausgabe Text    
130
  gfx_move(0,25);                // Angabe Ort
131
  gfx_hline(128);                // Ausgabe Strich  
132
    gfx_move(25, 35);              // Angabe Ort
133
  gfx_set_proportional(1);          // Angabe Schrift
134
  gfx_print_text("Funktion mit Timer");  // Ausgabe Text    
135
136
while(1)
137
{
138
139
// Beginn Timer Schleife
140
if (wait10 == 0xFF)             // Abfrage wai10 - Merker
141
  {                   
142
  wait10_merker=1;
143
    wait10=0;    
144
  }
145
146
if(get_key_short(1<<KEY_1))
147
  {                      // LED an
148
    leds_set_status(1,2);   
149
  }
150
151
if(get_key_long(1<<KEY_1))
152
  {                      // LED aus
153
    leds_set_status(0,2);   
154
  }
155
  
156
// LED Ansteuerung 6                 
157
if (wait10_merker == 1) 
158
  {                      // Steuerung LED 6 rot
159
    led5++;     
160
    if ( led5 == 50 )             // Einstellung Zeit 10ms mal x
161
    { 
162
      led5 = 0;
163
      PORTE ^= (1<<PE6); 
164
    }
165
  }
166
167
// LED Ansteuerung 7
168
if (wait10_merker == 1) 
169
  {                       // Steuerung LED 7 grün  
170
    led6++;
171
    if ( led6 == 100 )             // Einstellung Zeit 10ms mal x
172
    { led6 = 0;
173
        PORTC ^= (1<<PC7); 
174
    }
175
  }
176
177
// LED Ansteuerung 5 grün Blinklicht    // mit Abfrage aus                                                                                
178
if (wait10_merker == 1) 
179
  {                   
180
       led4++;
181
     if (led4 == 35 )            // Einstellung Zeit 10ms mal x
182
  { 
183
    led4 = 0;                                               
184
      if(!(PINC&(1<<PC5)))                // Abfrage PortC PC5 LED 5 Grün aus?                      
185
      leds_set_status(1,5);               // schaltet LED 5 Grün auf ein
186
      else
187
      leds_set_status(0,5);               // schaltet LED 5 Grün auf aus
188
    }  
189
  }  
190
191
wait10_merker = 0;              // Wait10_merker auf 0 setzen
192
193
  }                      // while
194
return 0;        
195
}                      // main
Viel Spass an alle Nutzer
achim

von Uwe S. (de0508)


Lesenswert?

Guten Abend,

es gibt doch noch Fehler, alle Variable, die aus ISR heraus verändert 
werden, benötigen ein |volatile| voran gestellt !


Achim Seeger schrieb:
> uint8_t key_state;
> uint8_t key_press;
> uint8_t key_rpt;
1
volatile uint8_t key_state;
2
volatile uint8_t key_press;
3
volatile uint8_t key_rpt;

Das könntest Du auch in meinem Beispielcode finden.

_

von Tommy T. (tommy2000)


Lesenswert?

Uwe S. schrieb:
> es gibt doch noch Fehler, alle Variable, die aus ISR heraus verändert
> werden, benötigen ein |volatile| voran gestellt !
>
Finde ich jetzt nicht die beste Lösung, aber das ist mit Sicherheit die 
einfachste.

Ich bevorzuge eine ordentliche critical section, die mit 
Compiler-Barriers markiert wird, beim GCC mit

asm volatile ("" : : : "memory");

Warum sollte ich innerhalb der CS auf Optimierungen verzichten? Statt 
sei und cei ruft man einfach ein Makro enter_critical_section und 
leave_critical_section auf, das neben der jeweiligen 
Interrupt-Konfiguration noch obige Compiler-Barrier enthält.

Grüße Tommy

von Achim S. (achims)


Lesenswert?

Hallo Uwe
wo finde ich deinen Beispielcode?
achim

von Uwe S. (de0508)


Lesenswert?

Guten Morgen

Achim Seeger schrieb:
> Hallo Uwe
> wo finde ich deinen Beispielcode?
> achim

Einfch dem Link in diesem Beitrag folgen:

Beitrag "Re: AVR-GCC Tutorial Taster entprellen"

von Achim S. (achims)


Lesenswert?

Hallo Uwe
das ist mir bekannt. Habe einen Ausdruck da von und gelesen. Dadurch ist 
ja die ganze Sache für den Atmel 128 gekommen und die Anpassung dazu.
Leider ist die Erklärung der einzelnen Funktion z.B. long, short usw 
nicht vorhanden. Dadurch muss man alles ausprobieren.
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim

Die Tastenentprellung ist genau dieselbe.
Nur hat Uwe den Code aufgeräumt und so zerlegt, dass er ihn leicht 
einbinden kann.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
Danke dir für deine Info. Werde die Sache noch mal unter dem Hinweis 
nachgehen. Es gibt noch einiges (viel) für mich zu klären und zu 
begreiffen
achim

von Tommy T. (tommy2000)


Lesenswert?

Hi Achim,

key_rpt wird verwendet um eine Taste zu wiederholen, so wie du am PC 
auch vieeeeeeeeele "e"s schreiben kannst, wenn du die "e"-Taste länger 
gedrückt hälst. Bei einem Mikrocontroller wäre Pfeil hoch/runter zum 
scrollen, aber wohl ein typischerer Anwendungsfall, als die Eingabe von 
Buchstaben.

Grüße Tommy

von Achim S. (achims)


Lesenswert?

Hallo Tommy
kann ich die Funktion auch ändern?
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:
> Hallo Tommy
> kann ich die Funktion auch ändern?
> achim

Welche Funktion?

Wenn du einen Autorepeat haben willst, dann musst du
* im Makro da oben die Taste als potentiellen Autorepeat Kandidaten
  anmelden
* im Code eine entsprechende Abfrage machen


zb
1
....
2
3
  while( 1 )
4
  {
5
6
    if( get_key_press( 1 << KEY_PLUS ) ||
7
        get_key_rpt( 1 << KEY_PLUS ) )
8
    {
9
      Wert++;
10
      mach was mit Wert
11
    }
12
13
    if( get_key_press( 1 << KEY_MINUS ) ||
14
        get_key_rpt( 1 << KEY_MINUS ) )
15
    {
16
      Wert--;
17
      mach was mit Wert
18
    }
19
  }

Wert wären zb. die Minuten einer Uhr oder der PWM Wert für eine 
LED-Dimmung. Drückst du einmal auf die Taste(n) veränderst du den Wert 
um jeweils 1. Bleibst du auf der Taste drauf, dann beginnt der Wert 
(durch den Autorepeat) sich automatisch schnell zu vergrößern (oder 
verkleinern). Du hast also 2 Fliegen mit einer Klappe: Du kannst bei 
Bedarf gezielt den Wert in kleinen Schritten einzeln 
vergrößern/verkleinern. Musst du den Wert aber über eine größere 
'Strecke' verändern, dann bleibst du einfach auf der Taste drauf und 
tippst dir keinen Wolf. Dieses Feature hat wohl jeder schon mal gesehen. 
Die meisten Nachtkästchen-Uhren haben sowas bei der Zeiteinstellung. 
Oder dein Fernseher bei der Lautstärkeeinstellung.

Und durch die Trennung von normalem Tastendruck und Autorepeat 
Tastendruck könnte man auch sowas programmieren
  * normaler Tastendruck:  Wert verändert sich um jeweils +-1
  * Autorepeat:            Wert verändert sich um beispielsweise +-10

Dann kann ich auch dann einen Wert in 0 komma nix auf den gewünschten 
Zahlenwert einstellen, wenn der Einstellbereich zb 0 bis 65535 lautet. 
Mit einem etwas längerem Autorepeat Tastendruck, der mir den Zahlenwert 
um jeweils 10 erhöht, manövriere ich mich in die Umgebung der 
gewünschten Zahl und die Feinjustage mach ich mit einzelnen 
Tastendrücken. Das funktioniert ziemlich intuitiv. Damit kommen Menschen 
sofort klar.

Die Tastenfunktionen stellen die Grundfunktion zur Verfügung. Durch die 
Art wie du sie einsetzt, kannst du es deinem Benutzer angenehm oder 
unangenehm machen, dein Gerät zu bedienen. Also was willst du da an den 
Funktionen selber ändern? Diese Funktionen sind Bausteine! Du 
entscheidest, wie du sie einsetzen willst.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
danke für deine Antwort. Beim testen der einzelnen Funktionen war mir 
die Anwendung der Taste mit rpt nicht ganz klar. Hatte als Anzeige auch 
nur eine LED geschaltet. Dadurch war die Funktion wahrscheinlich auch 
nicht erkennbar. Das mit deinen tasten gefällt mir sehr gut. Werde es 
ausprobieren.
Danke
achim

von Achim S. (achims)


Lesenswert?

Hallo
ich muss noch mal was fragen. Ich hatte bisher eine Textanzeige z.B. so 
gemacht:
1
for (x=0;x<=84;x++)             // Schleife
2
  {
3
    gfx_move(x,50);             // Angabe Ort
4
    gfx_print_text(" Text");    // Ausgabe Text
5
    delay(200);        // Pause
6
  }
Hat nur die Aufgabe durch for einen Punkt auf dem Display festzulegen. 
Da ich jetzt mit einem Timer arbeite, kann ich delay nicht mehr nehmen. 
Bei LED mache es so:
1
// LED Ansteuerung                 
2
if (wait10_merker == 1) 
3
  {                               // Steuerung LED 5 rot
4
    led5++;     
5
    if ( led5 == 50 )             // Einstellung Zeit 10ms mal x
6
      { 
7
        led5 = 0;
8
        PORTE ^= (1<<PE5); 
9
  }}
Es geht sehr gut. Ich kann delay im dem oberen Code nicht so einfach 
austauschen, da dort for drin ist bis 84 und delay mit 200ms.Habe dann 
versucht delay durch if und folgend auszutauschen. Leider geht dann gar 
nichts mehr. Arbeite mit einme Atmega 128 16M und Timer von 10ms. Ein 
anderer Code mit Timer und ISR steht weiter oben. Was mach ich wieder 
falsch?
achim

von Tommy (Gast)


Lesenswert?

Ich weiß zwar nicht warum meine Mail an dich hier im Thread erschienen 
ist, aber hat sich ja inzwischen auch erledigt.

Der Timer-Interrupt wird ja alle x Milisekunden aufgerufen, im Fall des 
Taster-Codes sind es alle 10ms. Statt jetzt mit einem delay von 200ms zu 
arbeiten kannst du auch einfach im Interrupt-Handler eine Variable 
hochzählen und immer wenn sie den Wert 20 errreicht führst du den Code 
aus und setzt sie auf 0 zurück (20*10ms = 200ms).

Gruß Tommy

von Achim S. (achims)


Lesenswert?

Hallo Tommy
das ist ok. Habe ich auch so bei den LED gemacht. Der Prg Aufbau ist 
doch bei Timer ganz anders. Da der Code doch alle ca. 10ms bearbeitet 
wird, werden auch alle nachfolgenden Befehle sofort ausgeführt. 
Beispiel: wenn ich zwei if Befehle nehme, so wird erst die eine 
abgearbeitet und wenn fertig die andere. Beim Timer muss ich doch if und 
andere Sachen so setzen, das so was nicht passieren kann.
Ich arbeite auch ser gern mit void und dem Aufruf zu Unterprogrammen. 
Dort schreibe ich dann wiederkehrende Funktionen oder ähnliches rein. 
Das klappt auch nicht mehr. Werde mal einen Flag versuchen, um die 
wiederholten Aufrufe zu unterbinden.
Habe da was angefangen, ohne auf die Durchführung zu achten. Timer haben 
was an sich.
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:

> Habe da was angefangen, ohne auf die Durchführung zu achten. Timer haben
> was an sich.


Das wichtigste ist:
Du musst deine Denkweise umstellen.

Bisher hast du das wahrscheinlich so gemacht

   zuerst mach das
   dann mach das
   und wenn du damit fertig bist, kommt dieses drann


Jetzt musst du anfangen, in Ereignissen zu denken und wenn notwendig, 
musst du dir eben selbst ein Ereignis generieren.

Deine Hauptschleife arbeitet sich dann der Reihe nach durch alle 
Ereignisabfragen durch und wenn das entsprechende Ereignis vorliegt, 
wird die zugehörige Teilaufgabe abgearbeitet.
Wenn diese Teilaufgabe beinhaltet, dass auf etwas gewartet werden muss, 
dann ist das Ende der Warterei ebenfalls einfach wieder ein Ereignis. 
Nämlich zb das Ereignis: Zeit abgelaufen; und wird genauso behandelt wie 
alles andere auch. Die Teilaufgabe 'wartet' also nicht aktiv, sondern es 
wird nur vermerkt, dass bei einem entsprechenden Ereignis in der Zukunft 
etwas zu passieren hat. Wie weit in der Zukunft - das ist dann die 
Wartezeit. Das was zu passieren hat, das ist dann das was nach der 
Warterei in dieser Teilaufgabe weiter zu passieren hat.

Der Timer ist auch nichts anderes als ein Ereignislieferant. Das 
Ereignis lautet bei dir: 10 Millisekunden sind vergangen. Und in der 
Hauptschleife lautet dann die entsprechende Fragestellung: Wenn jetzt 10 
Millisekunden vergangen sind, was gibt es dann genau jetzt alles zu tun. 
Die Fragestellung ist nicht: was gibt es in 2 Sekunden zu tun oder in 5, 
sondern genau jetzt. Was in der Zukunft passieren soll ist Sache eines 
anderen Durchlaufs durch die Hauptschleife, wenn das nächste 10 
Millisekunden Ereignis eintrifft.


Das ist das Um und Auf, wie man auf einem µC ein Programm schreibt, 
welches scheinbar mehrere Dinge gleichzeitig macht.

Alles andere, wie zb eine Statemachine, sind nur unterschiedliche 
Organisationsformen um dieses Grundprinzip zu implementieren.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
du bietset mir ganz schön was an. Bin am umdenken. Und dabei stelle ich 
fest, das es gar nicht so leicht ist. Muss ein grossteil meines 
bisherigen denkens zum Ablauf, so in altbewährter Art mit delay, 
wegschmeissen. Ist auch gleizeitig eine Herausvorderung für was neues 
und dabei ist noch viel zu lernen. Ist ein Ziel, hoffentlich nicht zu 
gross für mich.Werde dich bestimmt noch ein paar mal löchern müsse.
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:
> Hallo Karl Heinz
> du bietset mir ganz schön was an. Bin am umdenken. Und dabei stelle ich
> fest, das es gar nicht so leicht ist. Muss ein grossteil meines
> bisherigen denkens zum Ablauf, so in altbewährter Art mit delay,
> wegschmeissen.

Genau darum gehts.
Diese _delay_ms haben (ausser in Sonderfällen wenn es wirklich nur um 
ganze kurze Zeiten geht) nichts in einem Programm verloren. Sobald du so 
einen _delay_ms benutzt, hast du schon einen taktischen Fehler gemacht.

Diese delays werden konzeptionell immer gleich ersetzt:
Ein Timer erzeugt einen Basistakt von bekannter Zeitdauer und dann wird 
entsprechend oft mitgezählt, wieviele derartige Ereignisse aufgetreten 
sind bzw. auftreten müssen, bis dann die 'Wartezeit' abgelaufen ist und 
es weitergeht.
Aber nicht so

    cnt = 6000;    // warte 6 Sekunden bei 10ms Basistakt

    while( cnt > 0 ) {
      if( wait_10ms ) {
        wait10ms = 0;
        cnt--;
      }
    }

das bringt dir nichts. Das ist wieder eine Warteschleife, nur mit 
anderen Mitteln.

Sondern so:
1
int main()
2
{
3
  ....
4
5
  cnt = -1;                 // die für LED1 zuständige 'Eieruhr' ist inaktiv
6
                            // -1 bedeutet      abgeschaltet
7
                            //  0 bedeutet      die Uhr ist abgelaufen
8
                            //  positive Werte  die noch zu wartende Wartezeit in
9
                            //                  10ms Einheiten
10
11
  while( 1 ) {              // das ist die einzige Schleife die dauernd
12
                            // läuft
13
14
    ...
15
16
    if( get_key_press( .... ) )      // 1. Ereignis - Tastendruck
17
    {
18
      cnt = 6000;                    // Lade die Eieruhr, die dann
19
                                     // runterzählt
20
      PORTB &= ( 1 << LED1 );        // und schalte eine LED ein
21
    }
22
23
    if( wait_10ms ) {                // 2. Ereignis: 10ms sind vergangen
24
25
      if( cnt > 0 )                  // Ereignis 2a. Die Eieruhr
26
                                     // ist eingeschaltet, also 10ms runterzaehlen
27
        cnt--;
28
    }
29
30
    if( cnt == 0 ) {                 // 3. Ereignis: die Eieruhr ist abgelaufen
31
                                     // d.h. die Zeit, die beim get_key_press
32
                                     // auf die Eieruhr eingestellt wurde, ist
33
                                     // vorbei
34
      PORTB |= ( 1 << LED1 );        // Led wieder ausschalten
35
      cnt = -1;                      // und die Eieruhr inaktiv stellen
36
    }
37
38
39
    wait_10ms = 0;
40
  }

Und das verallgemeinert jetzt auch simpel auf mehrere LED und mehrere 
Taster, wobei jeder Taster 'seine' LED einschaltet, die dann nach einer 
bestimmten Zeit wieder ausgeht. Unabhängig davon, wieviele Taster bzw. 
LED, unabhängig davon ob die Zeiten Vielfache voneinander sind, 
unabhängig davon in welcher Reihenfolge die Tasten gedrückt werden und 
ob bereits eine Zeit läuft oder nicht wenn eine andere Taste gedrückt 
wird.
Wird eine Taste gedrückt, dann geht die LED an und nach einer bestimmten 
Zeit geht sie wieder aus. Das Konzept 'Eieruhr für die Wartezeit dieser 
LED' zusammen mit dem Ereignis 'Eieruhr abgelaufen' regelt das.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
du überraschst mich immer wieder. Hatt so wa ähnliche schon gemacht, 
aber auf -1 bin ich nicht gekommen, um etwas auszuschalten. Die Idee ist 
gut. Erinnert mich sehr an einen Flag. Kann bestimmt genommen werden um 
bestimmte void .. zu schalten oder den Aufruf zu sperren. Muss 
ausprobieren, wie ich es besten verwenden kann.
1
for (x=0;x<=84;x++)             // Schleife
2
  {
3
    gfx_move(x,50);             // Angabe Ort
4
    gfx_print_text(" Text");    // Ausgabe Text
5
    delay(200);                 // Pause
6
  }
7
8
for (x=84;x>=0;x--)             // Schleife
9
  {
10
    gfx_move(x,50);             // Angabe Ort
11
    gfx_print_text(" Text");    // Ausgabe Text
12
    delay(200);                 // Pause
13
  }
Im Moment bin ich noch an dieser Sache dran. Versuche gerade älter 
Sachen von mr auf Timer zu übersetzen. Dabei merkt man wo die Probleme 
sind.
Bei dem Code wird eine Text auf dem Bildschirn von links nach rechts und 
wieder zurück geschoben (auf meinem kleinen Display). Mit delay kein 
Problem. Das x gibt nur die Position an. So laufen beide Teile 
nacheinander ab. Das geht nicht mehr. Ersetze delay durch ein Teil vom 
Timer. Ich kann delay nicht so einfach austauschen. Der Timer läuft 
drüber und schaltet beide oder gar nicht. Es beinflusst for und der 
Timer. Habe die LED dann zur Kontrolle drin, ob es geht. Schalten die 
LED, hat sich nichts aufgehängt.
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:

> Bei dem Code wird eine Text auf dem Bildschirn von links nach rechts und
> wieder zurück geschoben (auf meinem kleinen Display). Mit delay kein
> Problem. Das x gibt nur die Position an. So laufen beide Teile
> nacheinander ab. Das geht nicht mehr. Ersetze delay durch ein Teil vom
> Timer. Ich kann delay nicht so einfach austauschen.


Natürlich kannst du.

Der cnt, den ich da oben als 'Eieruhr' eingesetzt habe, ist ja nichts 
anderes als eine Variable, die in bestimmten Zeitabständen ihren Wert 
ändert.

Und für dein x gelten genau die gleichen Anforderungen. In bestimmten 
Zeitabständen muss es andere Werte annehmen. Die Zahlenwerte sind 
andere, aber das Prinzip ist genau das gleiche.

Die for-Schleife hingegen, brauchst du überhaupt nicht.

Die Umstellung auf Ereignisse bedeutet in letzter Konsequenz, dass viele 
Schleifen ersatzlos wegfallen. Die Schleifen hast du nur gebraucht, 
damit x seinen Wert ändert. Mit dem Ereignisansatz hast du einfach nur 
eine andere Form gefunden, mit der Variablen zeitlich gesteuert ihren 
Wert ändern können.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
habe das Beispiel von dir genommen und erst mal alles raus, was nicht 
rein muss. Dannhabe ich es geändert:
1
if (wait10_merker ==1)
2
  {
3
    textr++;
4
    if (textr == 84)
5
      {
6
        gfx_move(textr,50);    // Angabe Ort
7
        gfx_print_text(" Text");  // Ausgabe Text
8
        textr = 0;
9
      }  
10
  }
Habe jetzt ein Zeit Problem. Die 84 ist der letzet Pixel auf dem 
Display. Es läuft nicht mehr langsam über das Display somdern springt 
gleich zum Ende.
Der Rücklauf kann erst starten wenn es am Ende ist. Komme leider nicht 
klar.
achim

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
habe die Sache mit der Zeit und einiges anderes in den Griff bekommen. 
Der Text läuft über das Display. Von links nach rechts und umgekehrt. 
Was nicht kklappt ist die Gegenseitige Verriegelung. So das nur eine 
Richtung freigegeben ist. Danach die andere. Habe es mit textlauf 
probiert, geht aber nicht.
1
// Text 1 rechts oben
2
3
if (textlauf=1)
4
{
5
if (wait10_merker ==1)
6
  {
7
  textro++;
8
  text1o=textro/20;
9
    gfx_move(text1o,45);    // Angabe Ort unter Logo
10
    gfx_print_text(" N I B O  2");  // Ausgabe Nibo 2
11
  if (textro == 1680)
12
      {
13
    textro = 0;
14
    textlauf=2;
15
      }  
16
  }
17
}
18
19
// Text 1 links oben
20
21
if (textlauf=2)
22
{
23
if (wait10_merker ==1)
24
  {
25
  textlo--;
26
  text2o=textlo/20;
27
    gfx_move(text2o,45);    // Angabe Ort unter Logo
28
    gfx_print_text(" N I B O  2");  // Ausgabe Nibo 2
29
  if (textlo == 0)
30
      {
31
         textlo = 1680;
32
   textlauf=1;
33
      }  
34
  }
35
}
Zum Beginn des Prg setze ich textlauf auf 1, damit die Sache links 
beginnt. Geht aber nicht so. Was mach ich falsch?
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:
> Hallo Karl Heinz
> habe das Beispiel von dir genommen und erst mal alles raus, was nicht
> rein muss. Dannhabe ich es geändert:
>
1
> if (wait10_merker ==1)
2
>   {
3
>     textr++;
4
>     if (textr == 84)
5
>       {
6
>         gfx_move(textr,50);    // Angabe Ort
7
>         gfx_print_text(" Text");  // Ausgabe Text
8
>         textr = 0;
9
>       }
10
>   }
11
>
> Habe jetzt ein Zeit Problem.

Nein hast du nicht.
Du hast einen Programmfehler.

(d.h. vielleicht hast du auch ein Zeitproblem, mag sein. ABer erst mal 
hast du einen Fehler)

> Display. Es läuft nicht mehr langsam über das Display somdern springt
> gleich zum Ende.

Genau das was du programmiert hast.
Du gibst den Text erst dann aus, wenn die Textposition die 84 erreicht 
hat.

Wenn du eine Laufschrift haben willst, dann wärs wohl niocht verkehrt, 
wenn du den Text an JEDER Zwischenposition ausgeben würdest.

> if (wait10_merker ==1)
>   {
>     textr++;
         gfx_move(textr,50);    // Angabe Ort
         gfx_print_text(" Text");  // Ausgabe Text

>     if (textr == 84)
>       {
>         textr = 0;
>       }
>   }
> [/c]

Und wenn das dann zu schnell läuft, dann muss man eben mit einem weitern 
Zähler noch eine Verzögerung einbauen. Kommt das 'Zeitsignal' alle 10 
Millisekunden, dann kann man 10 davon abzählen und hat so dann auch eine 
Verzögerung von 1 Hunderstelsekunde.


Ich klink mich schön langsam aus.
Ich denke nämlich nicht, das es sinnvoll ist, da jetzt noch weiter unter 
die Arme zu greifen. Du musst deine Fehler selber machen und du musst 
sie auch selber beheben. Nur so lernst du. Wenn ich dir immer aus der 
Patsche helfe lernst du in letzter Konsequenz nicht, wie man seinen 
eigenen Fehlern auf die Spur kommt. Radfahren lernt man nur indem man 
auch vom Rad fällt.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
hatte mein Fehler bereits bemerkt und das Prg geändert. Habe auch noch 
eine zusätzlich Verzögerung eingebaut
[c]
if (wait10_merker ==1)
  {
    textro++;
    text1o=textro/20;
    gfx_move(text1o,45);            // Angabe Ort unter Logo
    gfx_print_text(" N I B O  2");  // Ausgabe Nibo 2
    if (textro == 1680)
      {
    textro = 0;
    textlauf=2;
      }
  }
Damit kann ich die Zeit sehr gut einstellen und auch die Position stimmt 
genau. Bleibt nur noch die Verriegelung. Hast noch einen Tip für mich. 
Als Grundlage für Timer und Prg ist das alles sehr gut. Kann dann 
weitermachen.
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:
> Hallo Karl Heinz
> hatte mein Fehler bereits bemerkt und das Prg geändert. Habe auch noch
> eine zusätzlich Verzögerung eingebaut
> [c]
> if (wait10_merker ==1)
>   {
>     textro++;
>     text1o=textro/20;
>     gfx_move(text1o,45);            // Angabe Ort unter Logo
>     gfx_print_text(" N I B O  2");  // Ausgabe Nibo 2
>     if (textro == 1680)
>       {
>     textro = 0;
>     textlauf=2;
>       }
>   }

Die Idee ist zwar naheliegend, ist aber nicht besonders gut.
Die DIvision kostet Zeit und ausserdem gibst du den Text an eine 
Position aus, an der er sowieso schon steht. D.h. du vergeudest 
mutwillig Zeit

   if (wait10_merker ==1)
   {
     verzoegerung++;
     if (verzoegerung == 20 )
     {
       verzoegerung = 0;

       textro++;

       gfx_move(textro, 45);            // Angabe Ort unter Logo
       gfx_print_text(" N I B O  2");  // Ausgabe Nibo 2

       if (textro == 84)
       {
         textro = 0;
         textlauf=2;
       }
     }
   }


So ist es besser. Jetzt fällt nur dann Rechenzeit an, wenn es auch 
tatsächlich etwas zu tun gibt.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
du hast von Zeitvergeudung gesprochen. Muss dazu noch was fragen. Habe 
nach meinem Code 4 x Text eingebaut. Dadurch entsteht eine sichtbare 
Versammlung des gesamten Codes. Dann habe ich deine Verzögrung rein 
genommen. Dadurch entsteht entweder Fehlermeldung, keine Funktion oder 
die Anzeige springt. Hängt da von ab, ob es bei + oder - drin ist. 
Versteh den Zusammenhang nicht.
achim

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
habe alles programmieren können. Es geht jetz die Verzögerung, links und 
rechtslauf, die Zeiten der LED stimmen, die Umkehrung am Ende der Zeile 
und eine Auswahl ob zuerst links oder rechts anfangen soll bei 2 Zeilen, 
die Display Anzeige ist korrekt. Alles im Timerbetrieb und ohne delay. 
Hast recht, Übung machts. Danke für deine Hilfe. Falls jemand Interesse 
am Code hat, stelle ich ihn rein. Ist aber für den Atmega 128 beim Nibo 
geschrieben.
achim

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
keine Angst, ich will dich nicht mit irgendwelchen Codes ärgern. Habe 
aber ein Problem, wo die Antwort in keinem Buch steht.
Da ich den Code zur Enrprellung verwende, habe ich insgesamt 4 Taster 
zur Verügung. Diese setze ich ein um ein Menu zu steuern, (Start, auf, 
ab, Enter). Diese Schalterauswertung geht anschlessend in ein void 
(Unterprg) und führt entsprechende Aufgaben aus. Ist die Sache mit 
diesen UPrg sinnvoll oder braucht es wieder zu viel Zeit. In den Unterrg 
verwende ich ebenfalls kein delay. Bestimte Oprationen führe ich aus 
oder nicht, in dem ich diese mit einem Flag sperre oder zulasse. Welche 
Alternativen kennst du noch zu diesem Ablauf (bei Timer Betrieb)? 
Kannman das so machen?
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:

> Kannman das so machen?


Ja sicher. Kann man so machen.

von Achim S. (achims)


Lesenswert?

Hallo Karl Heinz
habe deine Eieruhr programmiert. Leider klapp es bei der Zeit nicht. 
Habe die entsprechenden Stellen rausgesucht:
1
if(get_key_press(1<<KEY_1))  // Abfrage nach S3 neu
2
  {  
3
    flagstart=1;    // LED an
4
    vzzeit=5;
5
  
6
  }
7
8
if (flagstart==1)     // Abfrage wai10 - Merker
9
  {  
10
    verzoegerung=100;                     
11
    nibo_startzeit(15,45);   
12
  }
Erst der Aufruf und dann die Ausführung
1
if (wait10_merker==1)
2
  {
3
    if (verzoegerung>0)
4
      {
5
        verzoegerung--;}
6
        if(verzoegerung==0)
7
          {
8
            vzzeit--;
9
10
              sprintf(zahlstart, " %d", vzzeit);  // Bildung Wert
11
              gfx_move(zeitm+72, zeitn-1);    // Angabe Ort
12
              gfx_set_proportional(0);    // Angabe Schrift
13
              gfx_print_text(zahlstart);    // Ausgabe Variable
14
          }
15
      }
16
  }
Innerhalb der "verzoegerung" triit eine Zeitverzögerung auf. Kann 
eigentlich gar nicht sein, da alles über einen Timer läuft. Leider ist 
mir nicht klar warum. vzzeit ist mit 5 voreingestellt. Es sollen die 
Zeiten dann runter gezählt werden und durch sprintf dargestellt werden.
Geht auch nicht so. was mach ich wieder falsch?
achim

von Karl H. (kbuchegg)


Lesenswert?

Achim Seeger schrieb:
> Hallo Karl Heinz
> habe deine Eieruhr programmiert. Leider klapp es bei der Zeit nicht.
> Habe die entsprechenden Stellen rausgesucht:

Sorry.
Aber ab gewissen Programmkomplexitäten reagiere ich auf "entsprechende 
Stellen rausgesucht" nicht mehr.

Du siehst da drinn keinen Fehler, ich seh da drinn keinen Fehler. Also 
wird der Fehler wohl in dem Teil liegen, den du nicht gezeigt hast. Zb 
in den Teil in dem verzoegerung wieder von 0 auf einen Wert größer 0 
gesetzt wird.


PS:
Wird dir das nicht langsam zu blöd, für eine Zahlenausgabe immer 
denselben Code quer über dein Programm zu verstreuen? Schreib dir doch 
eine Funktion dafür
1
void gfx_print_int( int zeile, int spalte, int wert)
2
{
3
  char text[10];
4
5
  sprintf( text, "%d", wert );
6
  gfx_move( zeile, spalte );
7
  gfx_set_proportional( 0 );
8
  gfx_print_text( text );
9
}

und schon fallen dir in deinem eigentlichen Programm wieder erklecklich 
viele Programmzeile weg.
1
...
2
  if (wait10_merker == 1)
3
  {
4
    if (verzoegerung > 0)
5
    {
6
      verzoegerung--;
7
    }
8
    if(verzoegerung == 0)
9
    {
10
      vzzeit--;
11
      gfx_print_int( zeitm+72, zeitn-1, vzzeit );
12
    }
13
  }

Immer gleiche Operationen in einer Funktion zusammenzufassen IST 
sinnvoll, weil an der Aufrufstelle unnötige Komplexität herausgenommen 
wird und der Blick aufs wesentliche (die Programmlogik an dieser Stelle) 
frei wird.

von Achim S. (achims)


Lesenswert?

Hallo KH
danke für den Tip
eigentlich suche ich nach einer antwort auf eine einfache Frage:
Den Timer lasse ich in der while Schleife laufen. Wenn ich mit void 
rausspringe und dort ein Unterprg ausführen lasse, geht dort auch der 
Timer aufruf mit wait10_merker?
achim

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.