PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Menü mit Drehgeber



ManniMammut
28.07.2006, 17:19
Hallo Forum!

Ich möchte mir gerne ein Drehgebergesteuertes Menü für meinen Robo mit ATMega32 programmieren. Die Ausgabe erfolgt dann auf einem 4x27-LCD.

Das Auslesen des Encoders hat mit dieser Methode (https://www.roboternetz.de/phpBB2/zeigebeitrag.php?t=17120&highlight=drehgeber) schon so einigermaßen geklappt - nur hat sich der Encoder öfters in der Drehrichtung verhaspelt. Allerdings habe ich das Auslesen des Drehgebers in einer Endlosschleife übernommen. Vielleicht liegt es also auch daran.
Wie sollte man diese Dinger abfragen? Im Polling-Verfahren? Oder Hardware-Interrupts?
Mein Drehgeber hat auch einen eingebauten Drucktaster, mit dem ich dann Eingaben bestätigen möchte.

Dazu dachte ich mir folgendes: Der Drucktaster liegt auf einem Hardware-Interrupt-Pin und bei Druck auf den Selben gelangt man in das Hauptmenü. Durch dieses scrollt man dann bis zu seinem gewünschten Menüpunkt und wählt ihn mit einem weiteren Druck aus. Jetzt kommt man in ein Submenu, in welchem man eine Einstellung (z.B. einen Zahlenwert durch Drehen des Gebers auswählen) tätigen kann und mit einem letzten Druck bestätigt sowie wieder ins übergeordnete Menü kommt.

Sowas hat doch bestimmt schon mal jemand von Euch gemacht, oder?! Über Anregungen und Lösungsvorschläge würde ich mich freuen.

Schönes Wochenende, Manni

vajk
28.07.2006, 18:00
Hallo!
Glaube nicht, daß eine Endlosschleife die richtige Wahl ist :-)

Ich nutze zwei Drehgeber in meiner Steuerung. Sinnvoll ist es, einen Interrupt und eben zwei weitere Ports zu belegen. Du solltest Dir darüber gedanken machen, wie Du diese - am besten über einen Timer entprellst - da Du sicher mechanische verwendest. Ich hab mir das erspart und präzise, optische genommen (Fa. Megatron, ca. 18 EUr pro Stück inkl. Fingermuldendrehknopf).

Bei einem weiteren Projekt, verwende ich jetzt auch mechanische, weils auf den Preis ankommt. Ich werde jedoch keine mit Taster mehr verwenden, da der Ottonormaluser damit nicht gut umgehen kann ... .

Noch was, Taster frägst Du am besten immer über einen Timer -Job ab, dann klapps auch mit dem Entprellen.

Viel Erfolg,
Vajk

izaseba
28.07.2006, 18:26
Für diesen Zweck setze ich einen Pin des Gebers auf einen Ext. Interrupt Eingang und den zweiten auf einen beliebigen anderen.
In der ISR mußt Du nur nachschauen ob der zweite Eingang gesetzt ist, oder nicht, je nachdem hast Du dann in die eine, oder andere Richtung gedreht.

Zu entprellen schalte ich je einen 100 nF Kondensator zwischen Pin und GND, funktioniert bis jetzt sehr gut.

Gruß Sebastian

ManniMammut
28.07.2006, 18:55
Hallo!
Glaube nicht, daß eine Endlosschleife die richtige Wahl ist :-)

Das war auch nur ein schneller Testaufbau um mal zu sehen, ob's grundsätzlich funktionieren würde ;-)



Ich hab mir das erspart und präzise, optische genommen (Fa. Megatron, ca. 18 EUr pro Stück inkl. Fingermuldendrehknopf).

Ich hab optische für zwei Euro das Stück (mir-Elektronik sei dank ^^). Hab extra nochmal im DB nachgeschaut, als ich gesehen hab, dass Du das neunfache bezahlt hast.

@Izaseba: DAS ist mal noch ne Idee die mir noch gar nicht gekommen ist mit einem Ausgang auf den ext. Interrupt. Das werde ich jetzt mal noch ausprobieren.

Danke, Manni

vajk
28.07.2006, 19:12
das mit dem Int hab ich doch auch geschrieben :-)
> Sinnvoll ist es, einen Interrupt und eben zwei weitere Ports zu belegen
(Zwei Ports für 2.Signal und Taster)

Dein Schnäppchen war wohl ein Sonderposten .. oder ?

Im Datenblatt zu meinen, oder den mechnischen von Reichelt ist beschrieben, wie man die auswertet ...

Viel Spaß noch,
Vajk

ManniMammut
28.07.2006, 19:51
@vajk: Sorry, ist mir im Nachhinein auch gekommen, dass du das selbe meintest. Ich wollte Dich auf keinen Fall "unter den Tisch kehren" :D

Sonderposten? Hmmm, also als ich vor ~5 Monaten oder so das letzte Mal da war hatten sie noch ne rieeeeesige Kiste voll mit denen. Ich hab das DB mal angehängt. Das Bitmuster steht da natürlich auch drin. Steht in deinem Datenblatt noch was spezielles zur Auswertung mit µCs drin? BTW ich wusste gar nicht, dass Reichelt auch Drehgeber im Sortiment hat?!

Ich bin dann mal weiter am Testen,
Manni

PasstScho
28.07.2006, 21:29
Halllo,
Ich nutze auch einen Mechanischen Drehencoder.
Meiner kommt aus dem Conrad und ist mit Kondensatoren entprellt.
Am besten suchst du mal im Conrad nach dem Datenblatt.
Darin stand auch ein Ablauf-Diagramm für Software.
Nach dem habe ich den code gemacht:


void CheckEncoder() {
encoder.state = readEncoder(); // gibt zwei bits zurück z.b.: 0b10, je nach Status der Encoder-Kanäle

uint8_t yy = (encoder.state ^ encoder.state_last);

if (((encoder.state & 0b10)>>1) == (encoder.state & 0b01)) {
if (yy == 0b01) {
//cw
encoder.position++;
} else if (yy == 0b10) {
//ccw
encoder.position--;
}
} else {
if (yy == 0b10) {
//cw
encoder.position++;
} else if (yy == 0b01) {
//ccw
encoder.position--;
}
}
encoder.changed = 1;
encoder.state_last = encoder.state;
}


Am Besten pollst du die Methode.

MfG Alex

vajk
28.07.2006, 22:54
Danke fürs Datenblatt!! auch eine Kiste haben will!!!!

ManniMammut
29.07.2006, 13:40
Ich versuche mich jetzt hiermit das erste Mal an Interrupts und habe nach fleißigem Studieren der Register den folgenden Code ausgearbeitet. Ich möchte nur eine LED an Portb.2 ein- und ausknipsen, sobald ein Low an INT0 anliegt. Der Interrupt-Pin liegt mittels dem Taster des Encoders auf Masse (oder eben auch nicht, wenn er nicht gedrückt ist). Das ganze funktioniert leider noch nicht. Wo liegt mein Fehler?



#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>


void ioinit( void )
{
DDRB |= 0xff;
DDRD |= 0x00;
}


int main( void )
{
ioinit();

GIMSK |= ( 1 << INT0 );
MCUCR |= ( 1 << ISC01 );

sei();

for( ;; )
{

}

return 0;
}

SIGNAL( SIG_INTERRUPT0 )
{
PORTB ^= ( 1 << PB2 );
}

izaseba
29.07.2006, 14:36
Hallo,
Hast Du externe Pullups?
Ich habe mein Programm schnell überflogen, es ist für M8, ich weiß zwar nicht welchen µC Du hast aber um den Interrupt zu aktivieren habe ich INT0 im Register GICR gesetzt, vielleicht liegt es daran.

Gruß Sebastian

ManniMammut
29.07.2006, 16:53
Hallo,
das GICR-Register hat nix gebracht.
Externe Pullups hab ich jetzt ausprobiert und *bingo* es geht. Ist das GICR-Register jetzt eigentlich notwendig? Dann habe ich mal probiert statt der externen die internen Pullups zu aktivieren mit dem Effekt, dass wieder nichts ging. Wieso? Die internen Pullups für den ganzen Portd aktiviere ich doch mit PORTD = 0xff wenn man das Datenrichtungsregister für den Port auf 0x00 gesetzt hat, oder?
Jetzt bin ich gerade noch dabei den Drucktaster hardwareseitig zu entprellen. Das geht doch mit einem parallel zum Taster geschalteten Kondensator. Welche Kapazität sollte der haben? 100nF sollten eigentlich reichen, oder?
Letzte Frage: Ist es egal, in welcher Reihenfolge man die Bits in die Register setzt?

Vielen Dank für Eure Hilfe, Manni

izaseba
29.07.2006, 19:28
das GICR-Register hat nix gebracht.
Fiel mir so spontan ins Auge, aber ein Blick in die m8def.inc sagt:


.equ GIMSK =$3b
.equ GICR =$3b ; new name for GIMSK

Also es ist Jacke wie Hose was man benutzt.....


Die internen Pullups für den ganzen Portd aktiviere ich doch mit PORTD = 0xff wenn man das Datenrichtungsregister für den Port auf 0x00 gesetzt hat, oder?
Das ist richtig, warum es mit den internen nicht klappt,kann ich nicht sagen, vielleicht sind die Werte zu groß ?
Zum Entprellen sollte 100 nF ausreichen, hab ich auch bei mir so gemacht, klappt super.

Wie meinst Du das mit der Reihenfolge ?

Gruß Sebastian

ManniMammut
30.07.2006, 00:50
Hi!
Nachdem ich jetzt über die Tastergeschichte ungefähr weiß, wie die externen Interrupts funktionieren bin ich mittlerweile dazu übergegangen, jetzt mal den Drehgeber zu testen. Also einen Ausgang an den INT0 und den anderen an Portd.3 als beliebigen Eingang. Entprellen habe ich mit 100nF probiert, die ich jeweils nach Izasebas Aussage zwischen Pin und GND geschaltet habe.

Ergebnis: Mit dem folgenden Code prellt das ganze wie Sau und außerdem habe ich keine Ahnung, was der Drehgeber da macht. Egal in welche Richtung ich drehe, zählt die Variable "zaehler" ohne System rauf und runter. Interessanterweise zählt die Variable immernoch, wenn ich eines der beiden if(bzw. else)-Statements auskommentiere und in die dazugehörige Richtung drehe. Komisch.



#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <stdio.h>
#include "wintek2704.h"
#include "wintek2704.c"


int zaehler;
char zaehler_s[2]; //in char konvertierte zaehler-Variable

void ioinit( void )
{
DDRB = 0xff;
DDRD = 0x00;

PORTD = 0xff; //Pullups ein kann ja nicht schaden...
}


int main( void )
{
zaehler = 0;

ioinit();

lcd_init( LCD_DISP_ON );
lcd_gotoxy( 0, 0 );
lcd_puts( "Initialisiert" ); //Display geht

GIMSK |= ( 1 << INT0 );
MCUCR |= ( 1 << ISC01 ) | ( 1 << ISC00 ); //INT0 auf steigende Flanke konfigurieren

sei();

for( ;; )
{

}

return 0;
}


SIGNAL( SIG_INTERRUPT0 )
{
if( PIND & ( 1 << PIND3 ) ) zaehler++; //Abfrage der Drehrichtung
else zaehler--;

sprintf( zaehler_s, "%d", zaehler ); //zur Ausgabe auf's Display wird zaehler zu einem char-Array konvertiert

lcd_clrscr();
lcd_gotoxy( 0, 0 );
lcd_puts( zaehler_s );
}


Achja, mit "Reihenfolge" meinte ich, ob es einen Unterschied macht, in welches Register ich zuerst schreibe. Sprich ob es egal ist


GIMSK |= blablabla;
MCUCR |= blubblubblub;

oder


MCUCR |= blubblubblub;
GIMSK |= blablabla;

zu schreiben, weil sich vielleicht ein Register automatisch ändert, wenn in ein verwandtes Register geschrieben wird?!

Ich hoffe, ich hab meine Probleme verständlich beschrieben.

Gute Nacht dann endlich, Manni

izaseba
30.07.2006, 10:28
MCUCR |= ( 1 << ISC01 ) | ( 1 << ISC00 ); //INT0 auf steigende Flanke konfigurieren


und was ist wenn Du den Interrupt auf fallende Flanke einstellst ?
Du hast ja Pullups drin, dann wäre es sinnvoller wohl die fallende Flanke zu überprüfen ?

Ich habe mein Programm fast so wie Du Deins und wie gesagt, es prellt nichts :-k

Gruß Sebastian

ManniMammut
30.07.2006, 13:36
Neue Erkenntnis: Es ist wurst, welche Zustandsänderung ich an dem ext. Int abfrage. Ob steigende, fallende Flanke oder beides - es macht keinen Unterschied. Pull-Ups ein und aus bringen auch keinen Vorteil/Nachteil.

Was ich allerdings feststellen konnte, ist, dass wenn ich in einer bestimmten Raste bin und ich jeweils einmal nach links und rechts drehe, der Zähler um 4 steigt (jeweils einmal bei nach links und einmal nach rechts). Das kann ich beliebig wiederholen und somit auf riesige Zahlen kommen. So, wenn ich jetzt eine Raste weiter nach rechts drehe und wieder hin und her drehe, passiert gar nichts. Wenn ich jetzt wieder eine Raste weiter nach rechts gehe und hin und her drehe zählt der Counter wieder runter! Wieder eine Raste weiter passiert beim hin und her drehen wieder nix. Dieses Muster wiederholt sich dann ins Unendlliche. Ist nicht ganz leicht zu verstehen, wenn man's nicht sieht. Ich hoffe Ihr könnt mir folgen...
Kann das daran liegen, dass im Datenblatt von meinem Encoder drin steht, dass sich die Wahrheitstabelle alle 4 Positionen wiederholt?
Prellen tut das ganze anscheinend wirklich, weil mit meiner oben im Post beschriebenen "Methode" ja immer um zwei rauf und runter gezählt wird. Manchmal aber auch nur um eins oder drei.

@Sebastian: Was sagt mir, dass Dein Programm fast so aussieht, wie meins?
Welchen Drehgeber verwendest Du denn eigentlich?

Gruß, Manni

izaseba
30.07.2006, 15:49
dieses "fast" bezieht sich darauf daß ich mein Programm im Assembler geschrieben habe O:)
Hier ein Ausschnitt:


.org 0x0000
rjmp reset
.org INT0addr
rjmp encode
.org OVF0addr
rjmp time0
....
....
reset:
ldi tmp1,HIGH(RAMEND)
out SPH,tmp1
ldi tmp1,LOW(RAMEND)
out SPL,tmp1
ldi tmp1,(1<<encoder1)|(1<<encoder2) ; Pullups für den Encoder
out encoderport,tmp1 ; dito
ldi tmp1,(1<<PB1)|(1<<PB2)
out DDRB,tmp1
ldi tmp1,(1<<ISC01) ; fallende Flanke am INT0
out MCUCR,tmp1 ; erzeugt einen Interrupt
ldi tmp1,(1<<INT0) ; Externen Interrupt am PD2 erlauben
out GICR,tmp1

...
...

encode:
in tmpi1,SREG
push tmpi1
ldi tmpi1,0x00
ldi tmpi2,0x05
in tmpiL,OCR1BL
in tmpiH,OCR1BH
sbic encoderpin,encoder2
rjmp encode2
sub tmpiL,tmpi2
sbc tmpiH,tmpi1
rjmp encode_end
encode2:
add tmpiL,tmpi2
adc tmpiH,tmpi1

encode_end:
out OCR1BH,tmpiH
out OCR1BL,tmpiL
pop tmpi1
out SREG,tmpi1
reti


Ich habe diesen hier (http://www.csd-electronics.de/data/pdf/ECW1.pdf)

Gruß Sebastian

ManniMammut
30.07.2006, 22:42
Die Erlösung!
Durch Zufall bin ich jetzt gerade drauf gekommen, dass man sämtliche externe Beschaltung weglassen muss. Hört sich komisch an, ist aber so. Im DB steht der Hinweis externe 2,2kOhm Widerstände an die beiden Phasenausgänge zu hängen. Hatte ich natürlich brav gemacht. Weiterhin hatte ich jeweils einen 100nF-Kondensator vom Phasenausgang gegen GND gehängt, wie mir das Izaseba erklärt hat. Lässt man diesen ganzen Schnickschnack weg, geht's auf einmal wunderbar! Es prellt nix und ich freu mich, dass alles funktioniert. Es sind jetzt nur die internen AVR-Pull-Ups eingeschaltet.
Das Einzige, was mich noch stört, ist, dass der Drehgeber nur alle 4 Positionen ein Signal abgibt. Ist das nur bei meinem Modell so (s. DB, wo drin steht, dass sich das Bitmuster alle 4 Positionen wiederholt) oder ist das bei Euren Encodern auch so? Kann man da Softwareseitig noch was machen? Wenn die Dinger bei Euch jede Raste ein Signal geben, dann würde ich mir überlegen, längerfristig einen neuen zu kaufen, weil das jetzige Drehgefühl einfach etwas "schwammig" ist.

Ansonsten könnte ich jetzt mit dem Programmieren des Menüs anfangen. Wie zieht man sowas auf? Wie oben beschrieben, möchte ich ein Hauptmenü haben, in welchem man dann per Drehgeber einen Menüpunkt anwählen kann. In das Untermenü kommt man dann per Druck auf den Drehgeber. Wenn man dann dort seine Einstellung getätigt hat, dann verlässt man das Untermenü wieder mit einem weiteren Klick auf den Drehgeber (suzusagen "OK"-Funktion) und landet wieder im Hauptmenü. Schön wäre es, das ganze möglichst modular aufzubauen, um schnell mal einen Menüeintrag oder auch einen Unteruntermenüpunkt (also schon relativ verschachtelt) einbaun zu können.
Das ganze könnte dann so aussehen:


1. Menüeintrag
2. Menüeintrag
>> 3. Menüeintrag, der gerade angewählt ist
4. Menüeintrag


Nachdem die Hardware ja jetzt läuft, dürfen sich jetzt auch die Software-Helden hieran die Zähne ausbeißen ;-)

Vielen Dank nochmal für Eure Hilfe, Manni

PS: Jetzt habe ich die Drehgeber auch endlich beim Reichelt gefunden. Die sind dort unter "Drehencoder" gelistet. Wobei meine hier gegen Reichelts 10€-Teile wohl echte Schnäppchen sind...

vajk
30.07.2006, 23:12
Hey, Danke für den Tipp ! Reichelt hat jetzt auch welche !

Sogar optische ... allerdings habe ich von dem hersteller genau so ein Teil hier - allerdings einen mechanischen 62P22-H4, macht keinen stabilen Eindruck!

Als mechanische sind diese bei Conrad günsitger :-) !!!

Das mit Deinen 4 Steps nur ein Impulse .. wenn das nicht so im Datenblatt steht, dann stimmt wahrscheinlich was nicht mit der Soft ...

izaseba
30.07.2006, 23:49
sämtliche externe Beschaltung weglassen muss.
Sorry, daß ich Dir nicht helfen konnte...
Soll wohl heißen, daß Drehgeber nicht gleich Drehgeber ist :roll:
was Menuaufbau angeht, schau mal bei www.mikrocontroller.net nach, ich habe dort schon einiges drüber gelesen.

Gruß Sebastian

sast
31.07.2006, 15:07
Hi Manni,

ich gehe mal davon aus, dass dein Encoder zwei Ausgänge hat. Dann würde das Ganze nämlich wieder Sinn machen.

Wenn dem so ist, dann brauchst du zwei Eingänge um den Encoder auszuwerten. Die Reihenfolge ist dann zum Beispiel 11-10-00-01-11-10-00 und so weiter.

A __|--|__|--|__

B _|--|__|--|__|-

hoffe das ist verständlich. Je nachdem ob nach 11 die 10 oder die 01 anliegt kannst du die Richtung ermitteln.

sast

Edit: hab gerade mal ins oben gepostete Datenblatt geschaut. Es ist so wie ich gesagt habe. Wenn du nun bloß einen Ausgang mit dem Int abfragst zum Beispiel A und dann noch bei der HL Flanke, dann spricht er nur bei Zustandsänderung von 1X nach 0X an. Also egal was B macht und das geht natürlich nicht.

ManniMammut
31.07.2006, 16:03
Hallo sast,
Das was Du in deinem Post beschreibst, ist doch nur der Greycode. Das werte ich ja genau in meinem Code aus.
Deinen Edit-Absatz verstehe ich ehrlich gesagt nicht ganz. Heißt das, dass ich für Phase B einen zweiten externen Interrupt bräuchte, damit ich bei jeder "Raste" ein Ergebnis erhalte?

@Izaseba: Kein Thema. Du hast mir so gut es ging geholfen! Und mit mikrokontroller.net habe ich eine gigantische Quelle für diese Menü-Programmiererei. Auch wenn ich bei dem wenigsten sofort durchsteige :-k.

MfG, Manni

sast
31.07.2006, 16:25
Wenn ich deine Aussagen richtig verstehe, hattest du geschrieben, dass du den einen Ausgang über die fallende oder steigende Flanke auswertest. Dadurch ergibt sich natürlich der Sachverhalt, dass du nur jeden vierten Impuls misst.
Ob du das nun Greycode nennst oder nicht ändert nichts daran, dass du alle 4 Zustände verarbeiten musst um die komplette Auflösung zu nutzen.

sast

ManniMammut
31.07.2006, 17:00
Ok, verstanden. Ein Lösungsansatz dafür wäre also, den Pinchange-Interrupt auf steigende und fallende Flanke zu stellen und in der ISR beide Pins auf ihren Zustand abzufragen? Somit müsste man doch schonmal die doppelte Auflösung erlangen, oder?
Wenn ich dann noch die volle Auflösung nutzen möchte, dann müsste ich das ganze Verfahren noch auf einen zweiten ext. Interrupt ausweiten, richtig?

Übrigens: "Greycode" nenne nicht ICH das, sondern so heißt dieses Signal, wo sich immer nur ein Bit ändert.

Gruß, Manni

sast
31.07.2006, 17:08
Ja, genau das wollte ich damit zum Ausdruck bringen. Eigentlich wollte ich dir nur erklären, warum gerade jeder 4. Impuls etwas gemacht hat.

sast

ManniMammut
31.07.2006, 23:28
So, nach einigem Ausprobieren und rumgetüftle, sieht meine ISR jetzt so aus:


_delay_ms( 1 );

if( ( PIND & ( 1 << PIND2 ) ) && ( PIND & ( 1 << PIND3 ) ) ) zaehler--;
if( ( !( PIND & ( 1 << PIND2 ) ) ) && ( !( PIND & ( 1 << PIND3 ) ) ) ) zaehler--;
if( ( !( PIND & ( 1 << PIND2 ) ) ) && ( PIND & ( 1 << PIND3 ) ) ) zaehler++;
if( ( PIND & ( 1 << PIND2 ) ) && ( !( PIND & ( 1 << PIND3 ) ) ) ) zaehler++;


Der Interrupt wird bei jeder Zustandsänderung ausgelöst. Somit wird jetzt jeder zweite Tick erkannt.
Ausweiten könnte man das ganze jetzt noch, indem man wie oben schon gesagt, einen zweiten ext. Interrupt-Pin auf die zweite Phase ansetzt. Die Auflösung von 50% reicht mir, weil ich keinen weiteren externen Interrupt opfern möchte. Das Drehgefühl ist jetzt auch ganz angenehm.
Entprellt habe ich das ganze noch weiter mit dieser Mini-Pause. Die Pause habe ich schon runteroptimiert soweit es ging. Da geht nicht viel Rechenzeit drauf und es ist sowohl einfach als auch funktionell, weil jetzt gar nix mehr prellt.

Dann muss ich mich jetzt wirklich nur noch um das Coden des Menüs kümmern.

Gute Nacht, (euer für heute sehr zufriedener O:) ) Manni