Forum: Compiler & IDEs AVR: Drehgeber-Verhalten bei sehr schnellem Wechsel
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!
Läuft der Prozessor wirklich mit 8MHz? Fuses richtig?
@ 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
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.
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
@ 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
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.
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:
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
Wie sieht deine Schaltung aus? Pull-Ups an A und B?
MFG
Falk
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
@ 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
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
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
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
> 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.
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.
|