PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Drehimpulsgeber



Bumbum
12.09.2009, 13:34
Hallo Leute,

ich bin am verzeweifeln. Ich schaffe es einfach nicht vernünftig einen Drehimpulsgeber auszuwerten. Ich habe mir dafür jetzt einen extra guten (und teuren) Geber mit optischer Auswertung gekauft, um das Problem mit dem Prellen einigermassen in den Griff zu bekommen. Der Geber hat 32 Rastungen pro Umdrehung und ich würde gerne einen Impuls pro Rastung nach links oder rechts erhalten.

Ich habe dafür sogar extra einen Atmel Tiny 2313 verbaut, der nichts anderes zu tun hat wie den Encoder auszuwerten. Leider springt der Wert trotzdem oft in die falsche Richtung, wenn ich drehe. Egal bei welcher Geschwindigkeit.

Hier mal mein Code bis jetzt:



#define AVRGCC

#include <avr/io.h>
#include <compiler.h>

/**
PD2 Spur A
PD3 Spur B

PB0 links
PB1 rechts

1 2 3 4
A 0 1 1 0
B 0 0 1 1
**/

#define Links1 PORTB = (PORTB | (1<<PB0))
#define Links0 PORTB = ~(~PORTB | (1<<PB0))

#define Rechts1 PORTB = (PORTB | (1<<PB1))
#define Rechts0 PORTB = ~(~PORTB | (1<<PB1))

#define SpurA ((PIND & 0b00000100) == 0)
#define SpurB ((PIND & 0b00001000) == 0)

bool lastDir = TRUE;

U8 read (void)
{
if (SpurA)
{
if (SpurB)
return (3);
else
return (2);
}
else
{
if (SpurB)
return (4);
else
return (1);
}
}

void links (void)
{
U8 i1;

Links0;
for (i1 = 0; i1 < 0xFF; i1++)
asm volatile ("NOP");
Links1;
lastDir = FALSE;
}

void rechts(void)
{
U8 i1;

Rechts0;
for (i1 = 0; i1 < 0xFF; i1++)
asm volatile ("NOP");
Rechts1;
lastDir = TRUE;
}

int main (void)
{
U8 AlterStatus;
U8 NeuerStatus;
U8 i1;

DDRB = 0b00000011;
PORTB = 0b00000011;

DDRD = 0b00000000;
PORTD = 0b00001100;

for (i1 = 0; i1 < 0xFF; i1++)
asm volatile ("NOP");

AlterStatus = read ();

while (1)
{
NeuerStatus = read ();

if (AlterStatus != NeuerStatus)
{
switch (AlterStatus)
{
case 1: { switch (NeuerStatus)
{
case 2: { rechts (); break; }
case 3: { if (lastDir)
rechts ();
else
links ();
break;
}
case 4: { links (); break; }
}
break;
}
case 2: { switch (NeuerStatus)
{
case 1: { links (); break; }
case 3: { rechts (); break; }
case 4: { if (lastDir)
rechts ();
else
links ();
break;
}
}
break;
}
case 3: { switch (NeuerStatus)
{
case 1: { if (lastDir)
rechts ();
else
links ();
break;
}
case 2: { links (); break; }
case 4: { rechts (); break; }
}
break;
}
case 4: { switch (NeuerStatus)
{
case 1: { rechts (); break; }
case 2: { if (lastDir)
rechts ();
else
links ();
break;
}
case 3: { links (); break; }
}
break;
}
}

AlterStatus = NeuerStatus;
}
}

return (0);
}


Wer kann mir sagen wo es hängt, oder einen Code schicken, der auch funktioniert. Der Tiny arbeitet mit dne Werkseinstellungen der Fuses und ohne Quarz mit der internen Takterzeugung..

Viele Grüße
Andreas

Bumbum
19.09.2009, 14:47
Hallo,

ich muss diesen Thread mal wieder nach oben schieben. Mein Projekt neigt sich langsam dem Ende und jetzt merke ich langsam wie schlecht der Code oben funktioniert. Ich würde sagen gefühlsmässig gehen 50% der Impulse in die falsche Richtung, oder werden gar nicht erst erkannt.

Vielleicht liegt es auch an der Beschaltung? Da der Encoder optisch funktioniert habe ich die NPN-Ausgänge einfach direkt an die Eingänge des AVR gehängt und dessen internen Pull-Ups aktiviert.

Wer kann mir sagen, was ich zur Verbesserung tun kann?

Viele Grüße
Andreas

radbruch
19.09.2009, 16:01
Hallo

Ich kann es natürlich nicht testen:

#define AVRGCC

#include <avr/io.h>
//#include <compiler.h>

/**
PD2 Spur A
PD3 Spur B

PB0 links
PB1 rechts

1 2 3 4
A 0 1 1 0
B 0 0 1 1
**/

#define Links1 PORTB = (PORTB | (1<<PB0))
#define Links0 PORTB = ~(~PORTB | (1<<PB0))

#define Rechts1 PORTB = (PORTB | (1<<PB1))
#define Rechts0 PORTB = ~(~PORTB | (1<<PB1))

#define SpurA ((PIND & 0b00000100) == 0)
#define SpurB ((PIND & 0b00001000) == 0)

char spura_akt, spura_old, spurb_akt, spurb_old, count=0, dir=0;

int main(void)
{
Rechts0;
Links0;
spura_akt=spura_old=SpurA;
spurb_akt=spurb_old=SpurB;

while(1)
{
do // warten bis Bitwechsel erkannt wird
{
spura_akt=SpurA;
spurb_akt=SpurB;
}while((spura_akt==spura_old) && (spurb_akt==spurb_old));

if(spura_akt==spura_old) // Bitwechsel war bei Spur B
{
if(spura_akt)
{
if(spurb_akt) dir=1; else dir=0; // dir==1 ist rechts/aufsteigend
}
else
{
if(spurb_akt) dir=0; else dir=1;
}
}
else // Bitwechsel war bei Spur A
{
if(spurb_akt)
{
if(spura_akt) dir=0; else dir=1;
}
else
{
if(spura_akt) dir=1; else dir=0;
}
}

spura_old=spura_akt;
spurb_old=spurb_akt;

if(dir)
{
Rechts1;
Links0;
if(count<31) count++; else count=0;
}
else
{
Rechts0;
Links1;
if(count>0) count--; else count=31;
}
}
return(0);
}
Ich hoffe, ich habe es ungefähr getroffen. Der Motor dreht dauerhaft in Richtung der letzten Geberdrehung, der Zähler wird bei jedem Geberimpuls geändert (mit Nulldurchgang).

Gruß

mic

[Edit]Ähm, irgendwie verstehe ich gar nicht was du eigentlich bezwecken möchtest. Wie soll denn die Drehbewegung des Geber nach Ausgang Links-Rechts verteilt werden? Mit einer Anzeige könntest du den Zähler verfolgen und kontrollieren ob alle Geberimpulse erkannt werden/ ob der Nulldurchgang immer an der selben Stelle auftritt.

Bumbum
19.09.2009, 17:32
Hallo mic,

danke für deine Unterstützung. Dummerweise kann ich es gerade auch nicht testen, da ich gerade "Feierabend" gemacht habe. Ich habe privat kein Programmiergerät für Atmels und muss das immer am Wochenende oder nach der Arbeit mit dem Teil aus der Firma machen. Aber der Code schaut gut aus. Ich hoffe ich komme morgen zum testen.

Zum Zweck des ganzen: Das soll natürlich keinen Motor steuern, sondern zwei Tasten "tippen" (via Transistor), je nachdem in welche Richtung der Encoder gedreht wird, soll die entsprechende Taste kurz getippt werden. Deine Ausgabe am Schluß ist demnach falsch, aber das konntest du nicht wissen. Mir ging es ja um die Auswertung, und die schaut gut aus. (Die Ausgabe werde ich natürlich selbst ändern).

Wie bereits geschrieben ist sonst nichts weiters an den Atmel angeschlossen. Der Grund für den Extra-Atmel nur für diese Aufgabe ist, dass ich bis jetzt immer Probleme mit so einem Encoder hatte und das ganze deshalb mal isoliert und ohne "störende" andere Programmteile richtig sauber lösen wollte, um dann zu verstehen, wie es funktioniert.

Wenn es funktioniert werde ich berichten. Vielen Dank erst mal!
Andreas

Bumbum
20.09.2009, 15:13
Hallo noch mal,

ich habe deine Software gerade getestet und bin sehr zufrieden. Ich würde sagen nun funktioniert die Auswertung zu ca. 95%. Hier mal mein angepasster Code für meinen Anwendungsfall:



#define AVRGCC

#include <avr/io.h>

/**
PD2 Eingang Spur A
PD3 Eingang Spur B

PB0 Ausgang links
PB1 Ausgang rechts
**/

#define Links1 PORTB = (PORTB | (1<<PB0))
#define Links0 PORTB = ~(~PORTB | (1<<PB0))

#define Rechts1 PORTB = (PORTB | (1<<PB1))
#define Rechts0 PORTB = ~(~PORTB | (1<<PB1))

#define SpurA ((PIND & 0b00000100) == 0)
#define SpurB ((PIND & 0b00001000) == 0)

char SpurA_akt, SpurA_old, SpurB_akt, SpurB_old, count = 0, dir = 0;

int main(void)
{
DDRB = 0b00000011;
PORTB = 0b00000011;

DDRD = 0b00000000;
PORTD = 0b00001100;

Rechts0;
Links0;

SpurA_akt = SpurA_old = SpurA;
SpurB_akt = SpurB_old = SpurB;

while (1)
{

do // warten bis Bitwechsel erkannt wird
{
SpurA_akt = SpurA;
SpurB_akt = SpurB;

if (count > 0)
{
count--;
if (count == 0)
{
Rechts0;
Links0;
}
}
}
while ((SpurA_akt == SpurA_old) && (SpurB_akt == SpurB_old));

if (SpurA_akt == SpurA_old) // Bitwechsel war bei Spur B
{
if (SpurA_akt)
{
if (SpurB_akt)
Links1;
else
Rechts1;
}
else
{
if (SpurB_akt)
Rechts1;
else
Links1;
}
}
else // Bitwechsel war bei Spur A
{
if (SpurB_akt)
{
if (SpurA_akt)
Rechts1;
else
Links1;
}
else
{
if (SpurA_akt)
Links1;
else
Rechts1;
}
}

SpurA_old = SpurA_akt;
SpurB_old = SpurB_akt;

count = 4;
}

return(0);
}


Erstaunlicherweise funktioniert das ganze besser, wenn ich die Fuse aktiviere, die den internen Clock durch 8 teilt, also wenn der AVR langsamer läuft.

Wie könnte man das ganze jetzt noch verbessern, damit aus den 95% vielleicht 100% werden? In professionellen Geräten bekommen die das ja auch irgendwie hin.

Viele Grüße
Andreas

radbruch
20.09.2009, 15:40
Prima :)

Vielleicht prellt/schwingt sich der Geber kurz ein. Je langsamer der AVR-Takt umso geringer die Chance ein Prellen zu treffen:

...
while ((SpurA_akt == SpurA_old) && (SpurB_akt == SpurB_old));

count=10; while(count--) asm volatile ("NOP"); // Geber entprellen

if (SpurA_akt == SpurA_old) // Bitwechsel war bei Spur B
...


btw: Bei mir wird das NOP nicht rausoptimiert:

128 009a 7093 0000 sts count,r23
129 009e 872F mov r24,r23
130 .L11:
131 /* #APP */
132 00a0 0000 NOP
133 /* #NOAPP */
134 00a2 8150 subi r24,1
135 00a4 E8F7 brcc .L11


Wie machen sich denn die 5% Fehler bemerkbar? Woran erkennst du das?

Bumbum
20.09.2009, 21:00
Hallo mic,

die 5% passieren häufiger beim schnellen drehen. Entweder reagiert der Encoder dann gar nicht, oder es wird ein Impuls in die falsche Richtung ausgelöst. Wenn mal ein falscher Impuls dabei ist kommt es dann auch häufig vor, dass insgesamt 4 falsche Impulse generiert (natürlich pro Rastung beim Encoder) werden, bis sich das ganze wieder "gefangen" hat.

Ich habe zum testen einfach einen zweiten AVR mit angeschlossenem LCD an die Schaltung gehängt und lasse dort einen Zähler mit Balken anzeigen. Am Balken sieht man sehr schön wenn mal was in die falsche Richtung geht.

Ich werde mal etwas mit der "Entprell-Schleife" spielen. Vielleicht bringt es ja was.

Viele Grüße
Andreas

Bumbum
20.09.2009, 21:02
Nachtrag zum NOP:

asm volatile ("NOP");

wird in einer Schleife nicht rausoptimiert, da es ja expliziet so vom Programmierer gewünschter ASM-Code an dieser Stelle ist.

Ein einfaches

nop ();

würde aber rausoptimiert werden.

radbruch
20.09.2009, 21:48
Hallo

Wenn es nur beim schnellen Drehen auftritt könnten deine erzeugten Impulse zu lang sein:

count = 4;

Wenn beim Warten auf einen Flankenwechsel noch ein Links/Rechts-Impuls aktiv ist wird möglicherweise der Wechsel auf der falschen Spur erkannt. Dies könnte zum Erkennen einer falschen Zählrichtung führen. Vielleicht count verkleinern.

Oder:

while ((SpurA_akt == SpurA_old) && (SpurB_akt == SpurB_old));
Rechts0;
Links0;
...
damit der letze Impuls sicher beendet wird. (Eventuell anschliessend nochmals kurz verzögern damit rechts0 zu rechts1 bzw. links0 zu links1 sicher erkannt werden kann)

Noch ein Ansatz:

while (1)
{
SpurA_old = SpurA;
SpurB_old = SpurB;

do // warten bis Bitwechsel erkannt wird
Das würde helfen wenn die Auswertung zu lange dauert und in der Zwischenzeit erneut ein Spurwechsel auftritt.

Ich weiß immer noch nicht wie ich auf einen Motor gekommen bin :)

Gruß

mic

Bumbum
22.09.2009, 16:14
Hallo mic,

danke für dein Mitdenken. Der Count war wirklich zu lange. Das Problem mit den mehreren falschen Impulsen in Reihe ist nun behoben. (Das lag mit an der schlampigen Auswertung im AVR mit LCD dran. Dort habe ich den gleichen IRQ für den PinChange für links und rechts verwendet und in der Routine per IF geprüft welcher Eingang gekommen ist. Somit hat bei zu langem Count immer der linke gewonnen)

Ich würde sagen nun funktioniert die Auswertung zu 97%. Dein zweiter Ansatz ist mir auch schon eingefallen, hat aber leider nichts gebracht. 97% ist vermutlich stark übertrieben. In wirklichkeit ist es glaube ich viel schlimmer. Ich schaue mal, ob ich das mit der Kamera aufzeichnen kann, dann siehst du wie "schlimm" es noch ist.

Ich bin mit meinem Latein erst mal wieder am Ende. Wenn ich die Fuse für den Clock/8 wieder raus nehme, wird die Auswertung deutlich schlechter. Wenn ich allersings Nops zur weiteren Verzögerung einbaue auch. Es ist wie verhext. Sollte man vielleicht hardwareseitig probieren das ganze noch etwas zu entprellen?

Viele Grüße
Andreas

radbruch
22.09.2009, 17:53
Hallo


Da der Encoder optisch funktioniert habe ich die NPN-Ausgänge einfach direkt an die Eingänge des AVR gehängt und dessen internen Pull-Ups aktiviert.Eine größere Last (kleinerer PullUp) würde vermutlich die Flanken der Eingangssignale steiler machen. Jetzt wäre der richtige Augenblick mal in das Datenblatt des "extra guten (und teuren) Geber mit optischer Auswertung" zu schauen. Hast du einen Link?

Gruß

mic

Bumbum
22.09.2009, 18:21
Klar, hier: http://www.car-mp3.de/RNetz/C200_62P.pdf

oberallgeier
22.09.2009, 18:53
... Eine größere Last (kleinerer PullUp) würde vermutlich die Flanken der Eingangssignale steiler machen ...Hmmm, keine Ahnung ob das bei diesem Encoder passt. Ich habe mal eine LED-Batterie in Reihe schalten müssen. Dort habe ich die Flanken bei sehr schnellen Schaltvorgängen - im untersten µs-Bereich - sehr verbessert, indem ich dem Basiswiderstand des Schalt-Transistors einen Kondensator verpasst hatte. Das Ganze ist mit Oskarbildchen belegt und hier beschrieben, (https://www.roboternetz.de/phpBB2/zeigebeitrag.php?p=387767&sid=cd0d6b0e1792731fe8a07c550210f324#387767) der zu den Bildern gehörige Text fängt zwei Absätze VOR den Bildern an.

Bumbum
22.09.2009, 19:24
Hallo oberallgeier,

leider komme ich an die Transistoren nicht ran, da diese im Encoder verbaut sind. Es bleibt mir also erst mal nur kleinere Pull Ups dazuzulöten. Mal schauen, was das bringt.

Viele Grüße
Andreas

radbruch
22.09.2009, 19:56
Hallo

Aha, laut Datenblatt des Tiny2313 (http://atmel.com/dyn/resources/prod_documents/doc2543.pdf) (Seite 181 ganz unten) hat der interen PullUp zwischen 20k und 50k, für den Geber werden aber 10k vorgeschlagen. Bei zu großem PullUp würde möglicherweise der High-Pegel am AVR-Eingang nicht sicher erreicht werden. Das würde ich zuerst versuchen.

Dann steht im Geberdatenblatt noch das:

Logic Rise and Fall: less than 30 ms
...
Contact Bounce: less than 4 ms at make
and less than 10 ms at breakIch bin jetzt nicht so der TTL-Profi, aber ich habe das Gefühl, wir sind viel zu schnell mit unserer Abfrage! Der Tiny2313 läuft defaultmässig (mit /8-Prescaler an internen 8MHZ) mit 1MHz (Datenblatt S.25 "Default Clock Source"), ein NOP würde dann 1 µs verzögern. Bei Signalflanken kleiner 30ms wären das fast 30000 AVR-Takte? Warum ändert sich da was wenn wir count=4 verändern? Was ändert dein Clock/8-Fuse bei dem der Tiny dann eigentlich mit 8MHZ rennen sollte? Ich hätte erwartet, dass ein optischer Sensor viel schneller reagiert *grübel* (Dieser Ansatz ist vielleicht auch eine Sackgasse)

Die 32 Rastungen(=Schritte?) pro Umdrehung kann ich auch nirgends finden. Sind es vielleicht nur 16? Das steht in der Teilenummer verschlüsselt. Bei 16 Impulsen pro Umdrehung würden die Dimensionen stimmen:

MaxSpeed: 100RPM
MinRiseandFall: 0,030s

60Sek/(16Imp/Umdr * 100)=60/1600=0,0375s

Das würde bedeuten, der Pegel bleibt mindestens 0,0075 Sekunden konstant? Lange Rede, kurzer Sinn: Du drehst einfach zu schnell ;)

Wenn die kleineren PullUps nicht helfen sollten wir mal die Bitlängen des Gebers bei schnellem Drehen messen:

#define AVRGCC

#include <avr/io.h>

/**
PD2 Eingang Spur A
PD3 Eingang Spur B

PB0 Ausgang links
PB1 Ausgang rechts
**/

#define Links1 PORTB = (PORTB | (1<<PB0))
#define Links0 PORTB = ~(~PORTB | (1<<PB0))

#define Rechts1 PORTB = (PORTB | (1<<PB1))
#define Rechts0 PORTB = ~(~PORTB | (1<<PB1))

#define SpurA ((PIND & 0b00000100) == 0)
#define SpurB ((PIND & 0b00001000) == 0)

char SpurA_akt, SpurA_old, SpurB_akt, SpurB_old, count = 0, dir = 0;
unsigned int count16;

int main(void)
{
DDRB = 0b00000011;
PORTB = 0b00000011;

DDRD = 0b00000000;
PORTD = 0b00001100;

while (1)
{
count16=0; // vorsichtshalber 16Bit-Zähler bereitstellen
count=5; // Dummywechsel (sollte ungerade sein)
while(!SpurA); // warten auf Startpegel
SpurA_old=SpurA; // das merken wir uns
while(count--) // Dummyspurwechsel um volle Drehgeschwindigkeit zu erreichen
{
while(SpurA==SpurA_old);
SpurA_old=SpurA;
}
// jetzt sollte der selbe Pegel wie beim Start gemessen werden
while(SpurA==SpurA_old) count16++;
// und nun steht in count16 die Zähllänge des Spurwechsels
}
return(0);
}Keine Ahnung ob das so funktioniert.

Gruß

mic

Bumbum
22.09.2009, 20:07
16 Impulse? Könnte natürlich sein, das ich mich da getäuscht habe... Ich zähle morgen mal.

Und wie soll ich von Hand denn zu schnell drehen? Ich vermute ich schaffe gerade mal so 1RPM. :-)

Ich werde erst mal den Lötkolben schwingen und Pull Ups rein löten. Mal gucken ob das hilft. Die Zeitmessung halte ich (erst mal) für unnötig, da ich sogar Richtungsfehler bekommen, wenn ich den Encoder gaaanz langsam drehe, also Rastung für Rastung.

thewulf00
23.09.2009, 08:16
Hallo Bumbum.

Ich habe viele von den billig-Encoder von Pollin und verbaue sie gern. Ich habe dazu eine -zugegebenermaßen nicht unbedingt optimale- Routine geschrieben, die 100% Funktion hat. Nachteil ist, sie funktioniert nicht über Interrupts, d.h. der Controller muss es im Hauptprogramm machen.

Das ganze habe ich, wie im Datenblatt empfohlen, mit einem kleinen Widerstandsnetzwerk entprellt.

Wenn Interesse besteht, kann ich das ja mal dokumentieren, aber nur, wenn Du es wirklich brauchst.

Bumbum
23.09.2009, 16:19
Hallo thewulf00,

klar, immer her damit. In meinem ersten Posting hier schreibe ich ja, das ich dafür extra einen Tiny "geopfert" habe, der nichts anderes macht wie den Encoder auszuwerten. Ich würde deinen Code gerne mal probieren!

Viele Grüße
Andreas

Bumbum
23.09.2009, 18:09
Nachtrag: Das Lötkolben schwingen hat geholfen. Ich habe jetzt 10k Pull-Up-Widerstände eingelötet und das ganze funktioniert perfekt. Ich würde sagen zu 100%.

Vielen Dank an alle, die geholfen haben!

@thewulf00: Dein Code wäre trotzdem interessant.

Viele Grüße
Andreas

radbruch
23.09.2009, 18:43
Juhu :)

thewulf00
23.09.2009, 19:44
Ja ok gern.
Aber nicht lachen, der Code funktioniert bei beliebigen Geschwindigkeiten.

Kurz zur Vorgeschichte: Dieser Code ist Teil eines Steuergerätes, das eine Unmenge an LEDs als Zimmerbeleuchtung steuern kann. Und zwar frei dimmbar von 0% auf 100%. Zudem gibt es zwei LED-Stromkreise, einen blauen und einen weißen. (Für romantisches und helles Licht) - Edit: Ich sehe gerade, den weißen Codeteil habe ich gestrichen, da ich für jemanden anderes nur die romantische Variante gebaut habe.

Wenn man die Encoder drückt, dimmt er das Licht an oder aus. Und nur, wenn das Licht an ist, kann man mittels drehen die Helligkeit steuern. Dreht man über 100% oder 0% hinaus, so ertönt ein Geräusch.


Achtung, unvollständig - Das Programm ist im kompletten sehr groß.



#define PIN_ENCODER PINB

// Blauer Encoder linker Kanal, rechter Kanal, Masse
#define PIN_ENCODER_B_L 1
#define PIN_ENCODER_B_GND 2
#define PIN_ENCODER_B_R 3

// Blauer Encoder Button
#define PIN_ENCODER_B_BTN 0
#define PIN_ENCODER_B_BTN_GND 4

// Ausgang für den blauen Stromkreis
#define PIN_PWM_B 3

// Sind beide Kanäle auf '1'?
#define ENC_B_BOTH (_BV(PIN_ENCODER_B_L) | _BV(PIN_ENCODER_B_R))
// Ist der linke Kanäle auf '1'?
#define ENC_B_LEFT _BV(PIN_ENCODER_B_R)
// Ist der rechte Kanäle auf '1'?
#define ENC_B_RIGHT _BV(PIN_ENCODER_B_L)

// Hilfsmakro
#define FILTER_ENC_B (PIN_ENCODER & ENC_B_BOTH)

#define PWM_CHANGE_VALUE 25

// Aktueller Programmzustand
#define MODE_OFF 0
#define MODE_FADE_IN 1
#define MODE_ON 3
#define MODE_FADE_OUT 4

SIGNAL (SIG_PCINT)
{ // BTN blue pressed
if (PIN_ENCODER & PIN_ENCODER_B_BTN) // Nur bei LOW!
return;

if (blue_pwm_mode == MODE_FADE_IN || blue_pwm_mode == MODE_FADE_OUT)
return;

if (blue_pwm_mode == MODE_OFF)
{
blue_pwm_mode = MODE_FADE_IN;
pwm_fadewert_blue = 0;
}else{
blue_pwm_mode = MODE_FADE_OUT;
pwm_fadewert_blue = pwm_wert_blue;
}

pin_changed = 1;
}


int main(void)
{
uint8_t oldB;
uint8_t newB;
uint8_t peep_no;


unsigned int* rot;
rot = &OCR1A;

pins_init();
timer1_init();
sei();

_delay_ms(250);

peep_ack();
peep_ack();


oldB = FILTER_ENC_B;

while (1)
{

if (pin_changed)
{
pin_changed = 0;
peep_ack();
}

// MODE_ON heißt, die Lampen sind an
if (blue_pwm_mode == MODE_ON)
if (FILTER_ENC_B != oldB)
{
newB = FILTER_ENC_B;
peep_no = 0;

if ((newB == ENC_B_BOTH) && (oldB == ENC_B_LEFT))
{
if (pwm_wert_blue >= PWM_CHANGE_VALUE+1)
pwm_wert_blue -= PWM_CHANGE_VALUE;
else
peep_no = 1;

}else if ((newB == ENC_B_BOTH) && (oldB == ENC_B_RIGHT)){
if (pwm_wert_blue < 255-PWM_CHANGE_VALUE+1)
pwm_wert_blue += PWM_CHANGE_VALUE;
else
peep_no = 1;

}else if ((newB == 0) && (oldB == ENC_B_RIGHT)){
if (pwm_wert_blue >= PWM_CHANGE_VALUE+1)
pwm_wert_blue -= PWM_CHANGE_VALUE;
else
peep_no = 1;

}else if ((newB == 0) && (oldB == ENC_B_LEFT)){
if (pwm_wert_blue < 255-PWM_CHANGE_VALUE+1)
pwm_wert_blue += PWM_CHANGE_VALUE;
else
peep_no = 1;
}

if (peep_no == 1)
peep_ack();

oldB = FILTER_ENC_B;
}

}
}





Falls es jemanden interessiert, hier ist der Part für die Software-PWM

SIGNAL (SIG_OUTPUT_COMPARE0A)
{
static uint8_t pwm_counter;

pwm_counter++;
if (blue_pwm_mode == MODE_ON && pwm_wert_blue >= pwm_counter)
{
PORT_PWM |= _BV(PIN_PWM_B);
}else
if (blue_pwm_mode != MODE_OFF && blue_pwm_mode != MODE_ON && pwm_fadewert_blue >= pwm_counter)
{
PORT_PWM |= _BV(PIN_PWM_B);
}else
PORT_PWM &= ~_BV(PIN_PWM_B);


if (pwm_counter == 255)
{
if (blue_pwm_mode == MODE_FADE_IN)
{
pwm_fadewert_blue ++;
if (pwm_fadewert_blue >= pwm_wert_blue)
blue_pwm_mode = MODE_ON;
} else if (blue_pwm_mode == MODE_FADE_OUT)
{
pwm_fadewert_blue --;
if (pwm_fadewert_blue <= 1)
blue_pwm_mode = MODE_OFF;
}
}
}

Bumbum
23.09.2009, 20:35
Hallo thewulf00,

ich bin jetzt zwar nicht ganz durch deinen Code durchgestiegen, aber ich glaube im Pirnzip ist der große Kern es das gleiche wie der Code von Nic.

Kann es sein, das dein Code nur jede zweite Stellung des Encoders erkennt? (Ich kann es gerade mal wieder nicht testen)

Viele Grüße
Andreas

thewulf00
24.09.2009, 07:12
Mein Code erkennt jede Stellung, deshalb ist er ja so "kompliziert".
Der Code ist nicht so schwer.
Im untersten Teil werden 4 Fälle abgebildet. Es fallen bei einem Drehimpuls immer zwei Kanaländerungen an. Nur die Reihenfolge dieser beiden Änderungen gibt Aufschluss über die Richtung. Deshalb frage ich auch nur einen der beiden Zustände ab. Aber ich vergleiche natürlich mit dem alten Zustand vor der Drehung, so dass ich auf jeden Fall quasi durch vergleichen alle acht Fälle abbilde.

Bumbum
24.09.2009, 17:28
Ok, dann ist es wirklich das gleiche wie der Code von Nic. Ich bin aber heute wieder nicht zum probieren gekommen.

Übrigens mein erster Code macht ja im Endeffekt auch was ähnliches, nur noch vieeeeel komplizierter wie deiner. ;-)

RP6conrad
24.09.2009, 20:19
Warum wirden die INT0 und INT1 nicht verwendet ? Dafur ist doch eine ISR gedacht, um solche schnelle signalen zu verarbeiten. In meinen robby hang der drehgeber an INT0. In der ISR von INT0 wird dan der zweite kanal geprueft. Zaehlt 100% richtig. Der frequenz ist bei mir max 2000 Hz. Der INT0 wird activiert bei jede positive flanke.

Bumbum
24.09.2009, 21:41
Hallo RP6conrad,

ich stimme dir voll zu, normalerweise sind dafür die INT-Eingänge perfekt.

Aber da ich mit diesen Drehimpulsgebern schon so oft auf die Schnauze gefallen bin, wollte ich dieses Thema endlich mal seperat behandeln und eine funktionierende Lösung erarbeiten, bei der ich auch sehe woran es liegen kann, wenn die Auswertung nicht geht. Und zwar ohne das ganze "außenrum", dass vielleicht wieder störenden Einfluß haben könnte.

Ich finde das ist mir hier mit Hilfe der Leute im Forum perfekt gelungen. Der beste Lerneffekt ist doch, wenn man Fehler macht, diese ausbessert und auch noch weiß, warum der Fehler so aufgetreten ist. Genauso ist es hier passiert.

Viele Grüße
Andreas

thewulf00
25.09.2009, 08:54
Ich habe bewusst auf Interrupts verzichtet, weil dann die Auswertung schwieriger ist. Du musst ja zwei Kanäle betrachten. Und allein der Zeitpunkt des Eintreffens entscheidet über die Wirkung. Sowas ist für mich eine typische "Hauptprogramm-Aufgabe" mit Schleifen.

Bumbum
25.09.2009, 18:24
Aber das ist doch gerade der Vorteil von IRQ's. Die werden genau dann ausgeführt, wenn das Ereignis auftritt. In diesem Fall, wenn sich der Eingang ändert. Man verzichtet dann einfach auf die Schleife im Hauptprogramm und prüft in der IRQ-Routine am Start gleich den Status der beiden Spuren und kann damit seine Auswertung machen.

Bei mics erstem Code könnte man dann quasi auf folgendes Verzichten:



while (1)
{

do // warten bis Bitwechsel erkannt wird
{
SpurA_akt = SpurA;
SpurB_akt = SpurB;

if (count > 0)
{
count--;
if (count == 0)
{
Rechts0;
Links0;
}
}
}
while ((SpurA_akt == SpurA_old) && (SpurB_akt == SpurB_old));


und statt dessen in die IRQ-Routine nur folgendes schreiben:



if (SpurA_akt == SpurA_old) // Bitwechsel war bei Spur B
{
if (SpurA_akt)
{
if (SpurB_akt)
Links1;
else
Rechts1;
}
else
{
if (SpurB_akt)
Rechts1;
else
Links1;
}
}
else // Bitwechsel war bei Spur A
{
if (SpurB_akt)
{
if (SpurA_akt)
Rechts1;
else
Links1;
}
else
{
if (SpurA_akt)
Links1;
else
Rechts1;
}
}

SpurA_old = SpurA_akt;
SpurB_old = SpurB_akt;

thewulf00
25.09.2009, 19:55
Versuchs mal umzusetzen, dann reden wir weiter ;p

Bumbum
25.09.2009, 20:02
Praktischerweise habe ich bei meinem Tiny ja die beiden INT Eingänge belegt. Ich werde es also mal versuchen.

RP6conrad
25.09.2009, 20:07
Mein ISR code sieht so aus :

/**
* External Interrupt 0 ISR
* (ENCL)
*
*/
ISR (INT0_vect)
{
if(PINC&KMI_L){
mleft_dist++;
mleft_counter++; //writeString_P("ISR0");//controle interrupt
e_compass++;
delta_distance++;} //richting robby !!

else {
mleft_dist--;
mleft_counter--;
e_compass--;
delta_distance--;} //delta_distance voor odometrie !!

}

In main gibt dan die Aenderung von mleft_dist wieviel tics und in welche Richtung da passiert sein. Der zweite variable mleft_counter wird jeden 100 mS ausgewertet und gibt dan die Geschwindigkeit for. Die variabel e_compass ist dan das unterschied in Abstand zwischen Rechter und Linker Rad.

Bumbum
29.09.2009, 18:21
Hallo,

ich wollte mich noch mal melden, das ich es noch nicht geschafft habe den ISR-Code auszuprobieren. Sobald ich das habe, werde ich mich wieder melden, entweder mit Erfolgsbericht, oder mit neuen Problem. :-)

Viele Grüße
Andreas

thewulf00
29.09.2009, 19:09
Glückwunsch! Nur weiter so!

Bumbum
03.10.2009, 13:45
Hallo thewulf00,

ich habe gerade eben endlich Zeit gefunden das ganze mit ISR auszuprobieren. Es funktioniert tatsächlich genauso perfekt wie der andere Code. Hier mal meine Lösung:



#define AVRGCC

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

ISR (INT0_vect);
ISR (INT1_vect);

/**
PD2 Eingang Spur A
PD3 Eingang Spur B

PB0 Ausgang links
PB1 Ausgang rechts
**/

#define Links1 PORTB = (PORTB | (1<<PB0))
#define Links0 PORTB = ~(~PORTB | (1<<PB0))

#define Rechts1 PORTB = (PORTB | (1<<PB1))
#define Rechts0 PORTB = ~(~PORTB | (1<<PB1))

#define SpurA ((PIND & (1<<PD2)) == 0) //INT0
#define SpurB ((PIND & (1<<PD3)) == 0) //INT1

volatile char Reset = 0;

ISR (INT0_vect)
{
if (Reset == 0)
{
if (SpurB)
{
if (SpurA)
Rechts1;
else
Links1;
}
else
{
if (SpurA)
Links1;
else
Rechts1;
}

Reset = 4;
}
}

ISR (INT1_vect)
{
if (Reset == 0)
{
if (SpurA)
{
if (SpurB)
Links1;
else
Rechts1;
}
else
{
if (SpurB)
Rechts1;
else
Links1;
}

Reset = 4;
}
}

int main(void)
{
DDRB = 0b00000011;
PORTB = 0b00000011;

DDRD = 0b00000000;
PORTD = 0b00001100;

MCUCR = 0b00000101;
GIMSK = 0b11000000;

sei ();

while (1)
{
if (Reset > 0)
Reset--;

if (Reset == 0)
{
Links0;
Rechts0;
}
}

return(0);
}


Den kurzen Code von RP6conrad habe ich nicht ausprobiert, weil ich ihn gerade nicht ganz verstanden habe. Ich glaube da fehlt noch ein Teil?

Viele Grüße
Andreas

thewulf00
05.10.2009, 09:59
Nicht schlecht. Das versuche ich auch mal.

Bumbum
05.10.2009, 18:34
Funktioniert wirklich bestens. Ich hatte jetzt ein paar Tage Zeit damit zu spielen und es funktioniert wirklich genauso 100%ig wie der Code von mic.

Allerdings habe ich da jetzt keine Maßnahmen zur Entprellung dran, da ich ja extra dafür den optischen Geber gekauft habe. Wie das mit den billigen "mechanischen" funktioniert kann ich nicht sagen. Da könnte das Prellen mit den IRQ's schon Probleme geben. Aber da könnte man vielleicht mit einem höheren Startwert für Reset entgegen wirken.

Viele Grüße
Andreas