Forum: Compiler & IDEs AVR: Drehgeber-Verhalten bei sehr schnellem Wechsel


von Bernd B. (grisu)


Angehängte Dateien:

Lesenswert?

Guten Abend,

ich habe hier gerade ein kleines Problem, bei dem mich hoffentlich 
jemand in die richtige Richtung schubsen kann:

Das Programm (siehe Anhang) soll später mal dazu dienen, an einem AVR 
Mega16 einen Wert per Drehgeber einstellen zu können. Vorgehen:

- Wert auf LCD ausgeben (klappt)
- Drehgeber auslesen: Klappt. Dazu habe ich den Code von Peter D. 
benutzt (Danke!).

Jetzt das verbliebene Problem: Drehe ich mit hoher Geschwindigkeit 
abwechselnd schnell mit der Hand rauf und runter, zählt der Drehgeber 
irgendwann mit jedem Schritt neun Impulse rauf - statt nur einem. Drehe 
ich nur schnell in eine Richtung, funktioniert alles korrekt.

Das der Anwender sowas macht, ist zwar unwahrscheinlich - aber mich 
interessiert das trotzdem: Kann sich jemand von euch erklären, was da 
passiert?

Danke im Voraus!

von Simon K. (simon) Benutzerseite


Lesenswert?

Läuft der Prozessor wirklich mit 8MHz? Fuses richtig?

von Falk B. (falk)


Lesenswert?

@ Bernd B. (grisu)

Du hast einiges falsch verstanden und umgesetzt.

enc_delta ist die akkumulierte Änderung des Drehgebers. Wenn man die 
ausliest und in einem grösseren Zähler speichert, muss man sie ATOMAR 
zurücksetzen, siehe Interrupt. volatile ist in deiner Konstellation 
unnötig, dafür fehlt das volatile für count. Praktisch macht man das 
eher so. Wobei ich nicht genau weiss, ob das mit der Mischung von char 
(vorzeichenbehaftet) und unsigned short gut geht.

1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <stdlib.h>
4
#include <stdio.h>
5
6
#include "lcd-routines.h"
7
8
#define PHASE_A  (PIND & 1<<PIND5)//(PINC & 1<<PINC0)  // PINC.0
9
#define PHASE_B (PIND & 1<<PIND6)//(PINC & 1<<PINC1)  // PINC.1
10
11
volatile char  enc_delta;    // -128 ... 127
12
13
unsigned short count; //neu
14
15
/*
16
17
F?r sp?ter (Zerlegung der eingestellten Zahl in Vor-/Nachkommastelle)
18
uint8_t einer=0;
19
uint8_t zehner=0;
20
uint8_t hunderter=0;
21
uint8_t nachkomma=0;
22
*/
23
24
25
char Buffer[8];
26
27
int main( void )
28
{
29
30
31
  lcd_init();
32
33
  lcd_clear();
34
35
  TCCR0 = 1<<CS01;      //divide by 8 * 256 (at 8 MHz);
36
  
37
  TIMSK = 1<<TOIE0;      //enable timer interrupt
38
39
  count=1;
40
41
  sei();
42
43
for(;;)
44
{
45
46
/*
47
  zehner=(count / 100);
48
  einer=(count / 10);
49
  nachkomma=(count % 10);
50
*/
51
  // muss atomar sein!
52
  cli();
53
  count += enc_delta;
54
  enc_delta=0;
55
  sei();
56
57
  sprintf(Buffer, "%d dB  ", count);
58
  lcd_home();
59
  lcd_string(Buffer);
60
}
61
62
63
}
64
65
66
SIGNAL (SIG_OVERFLOW0)
67
{
68
69
 static char enc_last = 0x01;
70
    char i = 0;
71
72
    if( PHASE_A )
73
      i = 1;
74
75
    if( PHASE_B )
76
      i ^= 3;                // convert gray to binary
77
78
    i -= enc_last;            // difference new - last
79
80
    if( i & 1 )            // bit 0 = value (1)
81
    {      
82
      enc_last += i;          // store new as next last
83
84
      enc_delta += (i & 2) - 1;    // bit 1 = direction (+/-)
85
    }
86
87
88
}

MFG
Falk

von Simon K. (simon) Benutzerseite


Lesenswert?


von Bernd B. (grisu)


Lesenswert?

Simon K. wrote:
> Läuft der Prozessor wirklich mit 8MHz? Fuses richtig?

Ja, allerdings ohne externen Quarz. 8 MHz intern. Die Fuses habe ich 
gerade nochmal ausgelesen - sind 8 MHz.

von Bernd B. (grisu)


Lesenswert?

Nabend.

Erstmal schonmal vielen Dank dir und auch Simon für die schnelle 
Antwort.

Falk Brunner wrote:

> enc_delta ist die akkumulierte Änderung des Drehgebers. Wenn man die
> ausliest und in einem grösseren Zähler speichert, muss man sie ATOMAR
> zurücksetzen.

Danke, das hatte ich völlig vernachlässigt.

> volatile ist in deiner Konstellation
> unnötig, dafür fehlt das volatile für count. Praktisch macht man das
> eher so.

Damit klappt es leider gar nicht mehr (brauchbar): Jetzt erkennt er 
scheinbar nur noch ganz selten überhaupt Drehungen. Ich kann nicht mal 
ein genaues Muster erkennen - geschätzt zeigt er vielleicht noch für 
jede zwanzigste Rasterung einen anderen Wert an. (Und zählt nur noch 
gerade - die ungeraden Zahlen überspringt er scheinbar?!).

> Wobei ich nicht genau weiss, ob das mit der Mischung von char
> (vorzeichenbehaftet) und unsigned short gut geht.

Ich bin auch für Alternativvorschläge zu haben. Da fehlt mir einfach die 
Erfahrung. Der Bediener soll nachher per Drehgeber einen (immer 
positiven) Wert einstellen können. Genau genommen hat der Wert später 
vor- und Nachkommastelle. Das habe ich aber erstmal weggelassen, um die 
Fehlersuche zu erleichtern.

Trotzdem nochmals Danke!

Bernd

von Falk B. (falk)


Lesenswert?

@ Bernd B. (grisu)

>Damit klappt es leider gar nicht mehr (brauchbar): Jetzt erkennt er

Liegt wahrscheinlich an der Mischung signed/unsigend, Mach mal

volatile int8_t enc_delta;
int16_t count;

>jede zwanzigste Rasterung einen anderen Wert an. (Und zählt nur noch
>gerade - die ungeraden Zahlen überspringt er scheinbar?!).

Das ist normal, die meisten Drehgeber haben zwei Codewechsel 
zwischen den Rastungen.

>Ich bin auch für Alternativvorschläge zu haben.

In der Auswertung nutzt du immer

richtiger_count = count >>1;

>positiven) Wert einstellen können. Genau genommen hat der Wert später
>vor- und Nachkommastelle.

Festkommaarithmetik

MFG
Falk

von Bernd B. (grisu)


Lesenswert?

Falk Brunner wrote:

> Liegt wahrscheinlich an der Mischung signed/unsigend, Mach mal
>
> volatile int8_t enc_delta;
> int16_t count;

Keinerlei (auf dem LCD sichtbare) Änderung im Verhalten - funktioniert 
leider genausowenig.

>>Ich bin auch für Alternativvorschläge zu haben.
>
> In der Auswertung nutzt du immer
>
> richtiger_count = count >>1;

Mir erschliesst sich gerade nicht, worauf du hinaus möchtest - das kann 
aber auch durchaus an mir liegen. :-) Ich werde mal drüber schlafen und 
mir die Sache morgen nochmal anschauen.

Nochmals danke für die schnelle Hilfe.

von Bernd B. (grisu)


Lesenswert?

Nabend.

Bernd B. wrote:
> Falk Brunner wrote:

>>>Ich bin auch für Alternativvorschläge zu haben.
>>
>> In der Auswertung nutzt du immer
>>
>> richtiger_count = count >>1;
>
> Mir erschliesst sich gerade nicht, worauf du hinaus möchtest - das kann
> aber auch durchaus an mir liegen. :-) Ich werde mal drüber schlafen und
> mir die Sache morgen nochmal anschauen.

Ich bin leider nix schlauer. Weder was deinen Hinweis betrifft, noch 
brachte längeres Brüten über dem Codes etwas. Addiere ich im Interupt:
1
 zaehler += enc_delta;

zaehlt er, es geht aber irgendwann schief - was aber auch nicht 
verwundert. Addier ich wie von dir vorgeschlagen in der main.c
1
  cli();
2
  zaehler += enc_delta;
3
  enc_delta=0;
4
  sei();

geht praktisch nix (reproduzierbares).

Dein Hinweis mit "richtiger_count = count >>1;" hat sich mir leider 
nicht erschlossen... Trotzdem danke.

Bernd

von Falk B. (falk)


Lesenswert?

Wie sieht deine Schaltung aus? Pull-Ups an A und B?

MFG
Falk

von Bernd B. (grisu)


Lesenswert?

Falk Brunner wrote:
> Wie sieht deine Schaltung aus? Pull-Ups an A und B?

Ja, derzeit 47k, weil beim Bestücken gerade nix anderes greifbar war.
Da sich an diesem (ALPS-) Encoder keine lesbare Beschriftung mehr 
findet, kann ich leider auch nicht im Datenblatt nachschauen, ob das 
passt.
Das Kuriose ist ja: Wenn ich wie beschrieben fälschlicherweise in der 
Interupt-Routine erfasse, reagiert er beim Drehen auf jede Rasterung, 
zählt aber irgendwann nicht mehr nicht wie gewünscht, sondern "zu viel". 
Daher ist zumindest meine (Laien-)Vermutung, das es irgendwo in der 
Software hakt.

Grüße
Bernd

von Falk B. (falk)


Lesenswert?

@ Bernd B. (grisu)

>Daher ist zumindest meine (Laien-)Vermutung, das es irgendwo in der
>Software hakt.

Dann prüfe erstmal die elementare Funktion. Lass die die Pins A und B 
ständig auf dem LCD anzeigen. Wenn du jetzt den Knopf festhälst und 
langsam drehst, muss du den Graycode wie im Artikel Drehgeber in der 
richtigen Reihenfolge sehen.

MFG
Falk

von Bernd B. (grisu)


Angehängte Dateien:

Lesenswert?

Falk B. wrote:

> Dann prüfe erstmal die elementare Funktion. Lass die die Pins A und B
> ständig auf dem LCD anzeigen. Wenn du jetzt den Knopf festhälst und
> langsam drehst, muss du den Graycode wie im Artikel Drehgeber in der
> richtigen Reihenfolge sehen.

Das habe ich erstmal vertagt, als ich eher zufällig noch einen Drehgeber 
vom anderen Typ in der Bastelkiste gefunden habe. Ist aber nur aus 
Zeitgründen aufgeschoben - das Resultat interessiert mich auch.

Der andere Encoder funktioniert. Mit einem kleinen Schönheitsfehler: Er 
liefert pro Rasterung immer vier Impulse. Ich habe noch keine saubere 
Methode gefunden, das durch vier zu teilen, ohne das Werte verloren 
gehen?!

Das von mir zunächst verwendete:
1
cli();
2
zaehler += enc_delta>>2;
3
sei();

verschluckt fast alle Rasterungen.
Peter Danegger hatte in einem Thread folgende Methode vorgeschlagen:
1
cli();
2
if( !(enc_delta & 1)){
3
  zaehler += enc_delta>>2;
4
  enc_delta = 0;
5
  }
6
sei();

Das funktioniert "besser" - es verschluckt ab und zu Rasterungen und 
zählt häufiger trotzdem noch um zwei Impulse hoch oder runter - auch 
wenn nur eine Rasterung gedreht wurde?! Ich habe jetzt eine Stunde 
gegoogled und das Forum abgesucht, aber bisher weder den entscheidenden 
Tip noch meinen Denkfehler gefunden. Kann bitte jemand mein Brett vor 
dem Kopf lösen? :-)

Zweite offene Baustelle(nachrangig):
Ein zweiter Timer soll alle 0,5s nachschauen, ob sich was geändert hat. 
Irgendeinen Denkfehler habe ich da auch noch. Sobald ich Timer1 
einschalte, erscheint aenderung bzw. "---" alle 0,5s im Wechsel mit dem 
anderen Text im Wechsel auf dem Display, egal ob ich drehe oder nicht. 
Wieso geht das nicht wieder auf 0?!

Grüße
Bernd

von Peter D. (peda)


Lesenswert?

Bernd B. wrote:
> Peter Danegger hatte in einem Thread folgende Methode vorgeschlagen:
>
>
1
> cli();
2
> if( !(enc_delta & 1)){
3
>   zaehler += enc_delta>>2;
4
>   enc_delta = 0;
5
>   }
6
> sei();
7
>

Nein, das habe ich nicht.

Wenn Du meinen Code von 2 auf 4 Schritte erweiterst, dann mußt Du auch 
beide unteren Bits maskieren:
1
cli();
2
if( !(enc_delta & 3)){
3
  zaehler += enc_delta>>2;
4
  enc_delta = 0;
5
  }
6
sei();


Oder noch besser so (Rastposition egal):
1
  signed char tmp;
2
3
  cli();
4
  tmp = enc_delta;
5
  enc_delta = tmp & 3;  // bit 1,0 needed for debounce !
6
  sei();
7
  zaehler += tmp>>2;


Peter

von Bernd B. (grisu)


Lesenswert?

Peter Dannegger wrote:
> Bernd B. wrote:

>> Peter Danegger hatte in einem Thread folgende Methode vorgeschlagen:
[..]
> Nein, das habe ich nicht.
Entschuldigung!

> Wenn Du meinen Code von 2 auf 4 Schritte erweiterst, dann mußt Du auch
> beide unteren Bits maskieren:

Der Groschen fiel leider erst, als ich deinen Code gesehen habe.

> Oder noch besser so (Rastposition egal):
>
1
>   signed char tmp;
2
> 
3
>   cli();
4
>   tmp = enc_delta;
5
>   enc_delta = tmp & 3;  // bit 1,0 needed for debounce !
6
>   sei();
7
>   zaehler += tmp>>2;
8
>

Funktioniert einwandfrei. DANKE!

Jetzt bleibt nur noch das Problem mit der Variable "aenderung" übrig. 
Ich sehe gerade nicht, warum die zwar korrekt nach dem ersten Drehen von 
Timer1 auf 1 gesetzt wird, danach aber nie wieder zurück - zumindest, 
wenn meine Debugging-Ausgabe kein Unsinn ist. Ausnahme: Drehe ich auf 0 
zurück, spring auch aenderung wieder auf 0.

Grüße
Bernd

von Bernd B. (grisu)


Angehängte Dateien:

Lesenswert?

> Jetzt bleibt nur noch das Problem mit der Variable "aenderung" übrig.
> Ich sehe gerade nicht, warum die zwar korrekt nach dem ersten Drehen von
> Timer1 auf 1 gesetzt wird, danach aber nie wieder zurück - zumindest,
> wenn meine Debugging-Ausgabe kein Unsinn ist. Ausnahme: Drehe ich auf 0
> zurück, spring auch aenderung wieder auf 0.

Sorry, der vergessene Code lies sich irgendwie nicht nachträglich 
anhängen.

von Peter D. (peda)


Lesenswert?

Bernd B. wrote:
1
// Keine negativen Werte einstellbar:
2
  if (zaehler<0){ 
3
    zaehler=0;
4
  }
5
6
  sei();
7
  zaehler += tmp1>>2;


Das kann natürlich nicht gehen.
Du mußt das 0 testen hinter die Encoder-Addition setzen.

Sonst: 0 + (-1) = -1


Peter

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.