Forum: Mikrocontroller und Digitale Elektronik Menüsteuerung von Taster auf Drehencoder umbauen


von Max B. (theeye)



Lesenswert?

Hallo!

Ich möchte einen Drehencoder in ein bestehendes Projekt implementieren. 
Dieser soll dazu dienen durch ein Menü zu navigieren. Bisher läuft dies 
über Taster. Der Encoder funktioniert auch so, wie er soll. Ich benötige 
jedoch Hilfe die Taster in der Menüsteuerung gegen den Drehencoder zu 
ersetzen. Vermutlich ist es keine große Sache, komme aber trotzdem nicht 
drauf :-)

Was für mich besonders interessant war:
- das Tutorial Drehgeber
- dieser Thread: Beitrag "Encoder mit ATMEGA8"

Setup
- ATmega8 auf einem STK500 (Takt: 1MHz)
- LCD (HD 48870) auf Steckbrett an Port C
- Drehencoder ALPS STEC12E auf Steckbrett an Port D (Pin 6 und 7)
- Taster auf dem STK (auch auf dem Steckbrett möglich)

Code
Die einzelnen Code-Teile findet ihr im Anhang. Mein Programm habe ich 
auf das nötigste beschränkt. Und ja, ich muss an einigen Stellen noch 
aufräumen ;-). Zum Code:
- main: Meine Hauptdatei, sie enthält vor allem die Initialisierung des 
Timer1 (hat im Beispielprojekt keine Funktion mehr), Initialisierung des 
Timer0 (für das Entprellen), die Hauptschleife sowie einen 
auskommentierten Codeblock für das Testen des Drehencoders. Die 
eigentlichen Funktionen standen hier, sind aber für die Bedienung nicht 
relevant.

- debounce: Code aus dem Artikel Entprellung von Peter Dannegger

- encoder: Code aus dem Artikel Drehgeber für Encoder mit wackligen 
Rastpunkten (auch von Peter Dannegger?). Portiert auf den ATmega8 und 
Umbau auf die Verwendung von __flash statt PROGMEM.

- lcd_menu: Die Menüerstellung und -steuerung. Basiert auf dem Code von 
http://projects.higaski.at/.

- lcd-routines: Code aus dem Artikel 
AVR-GCC-Tutorial/LCD-Ansteuerung mit Änderungen für das Menüsystem 
und um Stings aus dem Flash ausgeben zu können (Menü soll als nächstes 
auf die Nutzung von __flash umgebaut werden).

Problem
In der lcd_menu.c werden die Taster für die Navigation definiert:
1
#define UP_KEY    get_key_press (1<<1)
2
#define DOWN_KEY  get_key_press (1<<0)
3
#define ENTER_KEY  get_key_press (1<<2)

Im Prinzip müsste man diese Definitionen für den Encoder umschreiben. 
Ich habe es erst einmal versucht direkt in der Funktion umzuschreiben. 
So sieht die Navigation in der lcd_menu.c aus:
1
void browse_menu(void)
2
{
3
  do
4
   {  show_menu();
5
6
     if (UP_KEY)
7
     {lcd_clear();
8
       selected = menu[selected].up;
9
     }
10
11
         if (DOWN_KEY)
12
     {lcd_clear();
13
         selected = menu[selected].down;
14
     }
15
16
     if (ENTER_KEY)
17
     {lcd_clear();
18
        if (menu[selected].fp != 0)
19
       menu[selected].fp();
20
21
       selected = menu[selected].enter;
22
     }
23
24
     DELAY_MS(50);
25
26
   }while(1);
27
}

Ich habe hier folgendes versucht:
1
void browse_menu(void)
2
{
3
  do
4
   {  show_menu();
5
6
     val += encode_read();
7
8
     if (val > last_val)
9
     {last_val = val;
10
       lcd_clear();
11
       selected = menu[selected].up;
12
     }
13
14
         if (val < last_val)
15
     {last_val = val;
16
       lcd_clear();
17
         selected = menu[selected].down;
18
     }
19
20
     if (ENTER_KEY)
21
     {lcd_clear();
22
        if (menu[selected].fp != 0)
23
       menu[selected].fp();
24
25
       selected = menu[selected].enter;
26
     }
27
28
     DELAY_MS(50);
29
30
   }while(1);
31
}


Das brachte mich allerdings nicht an das erhoffte Ziel. Im Prinzip habe 
ich versucht meinen Code-Block zum Test des Encoders aus der main auf 
die Menüsteuerung zu übertragen. Hier noch der genannte Codeblock aus 
der main:
1
int32_t val = 0;
2
3
  encode_init();
4
  sei();
5
6
  char Buffer[30];
7
8
  while(1)
9
  {
10
11
    static int32_t last_val = 256;
12
    val += encode_read();
13
14
    if (val != last_val)
15
    {
16
      last_val = val;
17
      ltoa(val, Buffer, 10);   // convert int into string
18
          lcd_clear();            // clear display home cursor
19
      lcd_string(Buffer);        // put converted string to display
20
    }
21
  }

Die Variablen val, last_val und Buffer habe ich beim Umbauen der 
Menüsteuerung in die encoder-Dateien übernommen und in lcd_menu.c die 
encoder.h eingebunden. Damit alle Änderungen klar sind, habe ich noch 
meinen Umbauversuch (= die encoder_edit.h, encoder_edit.c und die 
lcd_menu_edit.c) mit angehängt.

Ich freue mich auf eure Hilfe!

Gruß Max


P.S.: Der Vollständigkeit halber habe ich die defines in lcd_menu nicht 
entfernt. So kann es von anderen noch leichter weiter verwendet werden 
:-)

P.P.S.: Falls entgegen meiner Erwartung ein Schaltplan benötigt werden 
sollte, reiche ich diesen natürlich nach!

von Karl H. (kbuchegg)


Lesenswert?

Max B. schrieb:

> Das brachte mich allerdings nicht an das erhoffte Ziel.

Sieht aber im Prinzip nicht so schlecht aus.

Da val nur größer werden kann, wenn aus encode_read etwas positives 
rauskommt, bzw. kleiner werden kann, wenn encode_read etwas negatives 
liefert, könnte man auch auf die ganze aufsummiererei verzichten und 
sich ganz einfach den Rückgabewert von encode_read ansehen. Ist er 
ungleich 0 und positiv, hat der Benutzer in die eine Richtung gedreht. 
Ist er ungleich 0 und negativ dann in die andere Richtung.

Aber im Grunde spricht auch nichts gegen dein Prinzip.

hast du denn schon kontrolliert, ob an dieser Stelle aus dem encode_read 
überhaupt etwa ungleich 0 geliefert wird. Sprich: Ob der Encode in 
deinem richtigen Programm korrekt funktioniert? Das wäre wichtig, denn 
manchmal macht man bei der Übertragung von funktionierendem Code aus 
einem Testprogramm in die Produktionsversion blöde Fehler bzw. übersieht 
eine Wechselwirkung mit dem restlichen Code.

von Karl H. (kbuchegg)


Lesenswert?

mir fehlt in deiner main() irgendwie der Aufruf von encode_init().
Seh ich ihn nur nicht oder hast du den tatsächlich vergessen?

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:

> Aber im Grunde spricht auch nichts gegen dein Prinzip.

Da war ich zu vorschnell.
Doch, es spricht was dagegen.
Wozu sich 32 Bit Arithmetik einhandeln, wenn es gar nicht notwendig ist. 
Zum anderen hat man irgendwann einen Überlauf. Gut. Bei 32 Bit kann ein 
Benutzer schon ganz schön kurbeln, bis es so weit ist, aber prinzipiell 
.... Geht man einfach nach dem Vorzeichen einer nicht-0 Antwort von 
encode_read dann lösen sich beide Probleme ganz von alleine in Luft auf 
und einfacher wird der Code auch noch.

: Bearbeitet durch User
von Max B. (theeye)


Lesenswert?

Das wichtigste (und peinlichste) vorweg:
Karl Heinz schrieb:
> mir fehlt in deiner main() irgendwie der Aufruf von encode_init().
> Seh ich ihn nur nicht oder hast du den tatsächlich vergessen?
Das war es! Das ist heute schon das zweite mal, dass mich sowas verrückt 
macht... Ist Programmieren nicht ein schönes Hobby? ;-)
Naja, ich möchte ja auch mal für Erheiterung sorgen können :-)

Karl Heinz schrieb:
> hast du denn schon kontrolliert, ob an dieser Stelle aus dem encode_read
> überhaupt etwa ungleich 0 geliefert wird. Sprich: Ob der Encode in
> deinem richtigen Programm korrekt funktioniert?
Ja, mit dem auskommentierten Codeblock in der main. Zuerst gab es da 
auch die von dir angesprochenen (unvorhergesehenen) Probleme bzgl. 
Konflikte mit bestehendem Code. Aber, wie gesagt, in diesem 
auskommentierten Codeblock findet sich auch das "vergessene" 
encode_init().

Karl Heinz schrieb:
> Da val nur größer werden kann, wenn aus encode_read etwas positives
> rauskommt, bzw. kleiner werden kann, wenn encode_read etwas negatives
> liefert, könnte man auch auf die ganze aufsummiererei verzichten und
> sich ganz einfach den Rückgabewert von encode_read ansehen. Ist er
> ungleich 0 und positiv, hat der Benutzer in die eine Richtung gedreht.
> Ist er ungleich 0 und negativ dann in die andere Richtung.

Karl Heinz schrieb:
> Aber im Grunde spricht auch nichts gegen dein Prinzip.

Karl Heinz schrieb:
> Da war ich zu vorschnell.
> Doch, es spricht was dagegen.
> Wozu sich 32 Bit Arithmetik einhandeln, wenn es gar nicht notwendig ist.
> Zum anderen hat man irgendwann einen Überlauf. Gut. Bei 32 Bit kann ein
> Benutzer schon ganz schön kurbeln, bis es so weit ist, aber prinzipiell
> .... Geht man einfach nach dem Vorzeichen einer nicht-0 Antwort von
> encode_read dann lösen sich beide Probleme ganz von alleine in Luft auf
> und einfacher wird der Code auch noch.
Stimmt, das werde ich mal mit auf meine Verbesserungsliste setzen.

Danke für die schnelle Hilfe und den Hinweis ala "Da fehlt ein ;" nach 
5h Suchen (im übertragenen Sinne).

Gruß Max

edit: Jetzt werde ich das mal in die defines für die "KEY'S" übertragen

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Max B. schrieb:

> Danke für die schnelle Hilfe und den Hinweis ala "Da fehlt ein ;" nach
> 5h Suchen (im übertragenen Sinne).

Gerne. An dem Punkt war wohl jeder schon mal. Selber sieht man solche 
Sachen nicht.

von Max B. (theeye)


Lesenswert?

Karl Heinz schrieb:
> Selber sieht man solche
> Sachen nicht.

Ganz genau! Seit dem ich mit meiner Freundin zusammen wohne gibt es den 
Mitbewohner nicht mehr, der genau solche Dinge dann sehen konnte :-P

Gruß Max

von Max B. (theeye)


Lesenswert?

Einmal habe ich doch noch Tomaten auf den Augen:
Wenn ich encode_init() nicht vergesse, läuft mein Encoder auch. Was dann 
jedoch nicht mehr klappt sind Tastereingaben. Was übersehe ich?

Gruß Max

von Max B. (theeye)


Lesenswert?

Hier noch der Code der Initialisierung des Encoders. Befindet sich 
natürlich auch im ersten Poste, dann muss aber keiner suchen :-)
1
void encode_init( void )            // nur Timer 0 initialisieren
2
{
3
  TCCR2 = 1<<WGM21^1<<CS22;
4
  OCR2 = (uint8_t)(XTAL / 64.0 * 1e-3 - 0.5);
5
  TIMSK = (1<<OCIE2);
6
}

Der Timer für das Entprellen der Taster befindet sich in der main:
1
TCCR0 = (1<<CS02)|(1<<CS00);
2
TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);
3
TIMSK = (1<<TOIE0);

Gruß Max

: Bearbeitet durch User
von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Du benötigst gar keine 2 Timer, um Taster und Drehgeber abzufragen, das 
kann ein Timer ganz alleine. Da die Wiederholrate für den Encoder etwa 
10 mal höher sein sollte, lässt du im Timerinterrupt die Buttonroutine 
eben nur alle 10 mal durchlaufen, z.B.:
1
ISR(TIMER1_OVF1_vect) {
2
int8_t enc_new, diff;
3
static uint8_t ct0, ct1, rpt,btimer;
4
uint8_t i;
5
6
// rotary handling
7
  enc_new = 0;
8
  if( PHASE_A )
9
    enc_new = 3;
10
  if( PHASE_B )
11
    enc_new ^= 1;                   // convert gray to binary
12
  diff = enc_last - enc_new;        // difference last - new
13
  if( diff & 1 ){                   // bit 0 = value (1)
14
    enc_last = enc_new;             // store new as next last
15
    enc_delta += ((diff & 2) - 1);  // bit 1 = direction (+/-)
16
  }
17
  btimer++;
18
  if (btimer > 9) {
19
// button handling
20
    i = key_state ^ ~ROTARY_PIN;
21
    ct0 = ~(ct0 & i);  
22
   ct1 = ct0 ^ (ct1 & i);
23
   i &= ct0 & ct1;
24
   key_state ^= i;
25
    key_press |= key_state & i;
26
    if( (key_state & REPEAT_MASK) == 0 )            // check repeat function
27
         rpt = REPEAT_START;                          // start delay
28
  if( --rpt == 0 ){
29
        rpt = REPEAT_NEXT;                            // repeat delay
30
        key_rpt |= key_state & REPEAT_MASK;
31
  }
32
    btimer = 0;  
33
  }
34
}
Hier ist es ein Tiny26, Timer1, der beides macht.

: Bearbeitet durch User
von Max B. (theeye)


Lesenswert?

Matthias Sch. schrieb:
> Du benötigst gar keine 2 Timer, um Taster und Drehgeber abzufragen, das
> kann ein Timer ganz alleine.

Guter Hinweis! So wird der Code einfacher und ich habe noch einen Timer 
frei :-)

Dennoch würde ich gerne wissen, warum es mit dem aktuellen Code nicht 
funktioniert.

Gruß Max

von Karl H. (kbuchegg)


Lesenswert?

Max B. schrieb:

> Dennoch würde ich gerne wissen, warum es mit dem aktuellen Code nicht
> funktioniert.

Na dann überleg mal was
1
void encode_init( void )            // nur Timer 0 initialisieren
2
{
3
...
4
  TIMSK = (1<<OCIE2);
5
}
mit dem TIMSK Register macht, und was
1
...
2
  TIMSK = (1<<TOIE0);
3
...

in den Tastaturroutinen macht.
Denk nicht so sehr daran, was es mit dem einen angegebenen Bit macht, 
sondern daran, was es mit den ANDEREN Bits macht.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Karl Heinz schrieb:
> Denk nicht so sehr daran, was es mit dem einen angegebenen Bit macht,
> sondern daran, was es mit den ANDEREN Bits macht

Hehe, ich dachte, der TE hätte meinen 'Wink mit dem Zaunpfahl' (nur 
einen Timer) schon verstanden - dann fällt das mit dem TIMSK erstmal 
nicht auf. Nur, wenn dann doch noch ein Timer für andere Sachen eröffnet 
wird, is' wieder Banane...

von Max B. (theeye)


Lesenswert?

Karl Heinz schrieb:
> Denk nicht so sehr daran, was es mit dem einen angegebenen Bit macht,
> sondern daran, was es mit den ANDEREN Bits macht.

Matthias Sch. schrieb:
> Hehe, ich dachte, der TE hätte meinen 'Wink mit dem Zaunpfahl' (nur
> einen Timer) schon verstanden - dann fällt das mit dem TIMSK erstmal
> nicht auf.

Ich habe das Problem hiermit gelöst ;-)
1
|=

Danke :-)

Gruß Max

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.