Forum: Mikrocontroller und Digitale Elektronik Impulsgeber an µC


von Mathias B. (matthiasbuerkle)


Lesenswert?

Hallo Zusammen,

ich bin im Moment dabei, mich in die Welt der µC einzuarbeiten. Habe ein 
Board mit einem Atmega32 vor mir, an das ich einen 
Inkrementalimpulsgeber angeschlossen habe. Dieser gibt zwei 
Rechtecksignale aus, die um 90° verschoben sind. Soweit so gut.

Jetzt zum Problem:
Wenn ich den Impulsgeber langsam drehe, werden alle Impulse richtig 
eingelesen. Dreh ich den Impulsgeber schneller, dann werden nicht alle 
Impulse eingelesen. Meine Ideen dazu sind bis jetzt, dass der Eingang 
vielleicht nicht mit der hohen Signalfrequenz klar kommt. Im Datenblatt 
bin ich leider nicht fündig geworden.
Nach meiner Berechnung müsste der µC später im Betrieb dann zwei Signale 
von 40kHz einlesen.

Zum Aufbau:
Der Impulsgeber (1200 Imp/U) sitzt hinter einem Schrittmotor(1,8°). Der 
Schrittmotor treibt ein Getriebe (i=4) an. Das Getriebe treibt eine 
Spindel(p=2mm) an. Die max. Verfahrgeschwindigkeit soll 1000mm/min 
betragen.

Geht das Grundsätzlich?

Ich bin für jede Hilfe dankbar!!

Matthias Bürkle

von Falk B. (falk)


Lesenswert?

@  Matthias Bürkle (matthiasbuerkle)

>Wenn ich den Impulsgeber langsam drehe, werden alle Impulse richtig
>eingelesen. Dreh ich den Impulsgeber schneller, dann werden nicht alle
>Impulse eingelesen. Meine Ideen dazu sind bis jetzt, dass der Eingang
>vielleicht nicht mit der hohen Signalfrequenz klar kommt.

Der Eingang schon, deine Verarbeitung aber wahrscheinlich nicht.

Siehe Drehgeber

>Nach meiner Berechnung müsste der µC später im Betrieb dann zwei Signale
>von 40kHz einlesen.

= 25 us, macht bei 20MHz gerade mal 500 Takte pro Zyklus. Damit dürfte 
auch ein AVR schon ziemlich gut beschäftigt sein. Das kann man noch per 
Interrupt machen, besser wäre aber wahrscheinlich Polling.

>Geht das Grundsätzlich?

Sicher.

MFG
Falk

von Mathias B. (matthiasbuerkle)


Lesenswert?

Danke für deine Erklärungen. Ich werd mich morgen mal an den 
Beispielcode auf der Drehgeber-Seite setzen.

Wäre es vielleicht bessern, wenn ich vor die Eingänge einen externen 6:1 
Teiler aufbaue, damit der Prozessor nicht so ausgelastet ist?
Für die Auflösung des Schrittmotors würde das noch reichen.

Das Ganze soll nämlich mal einen Achsantrieb für einen CNC-Maschine 
werden. Also kommt noch die Steuerung für den Schrittmotor dazu. Hierfür 
brauche ich bestimmt auch noch mal ein wenig Rechenzeit vom Prozessor.

Danke schon mal und einen schönen Abend.

Matthias

von Christian R. (supachris)


Lesenswert?

Matthias Bürkle wrote:
> Wäre es vielleicht bessern, wenn ich vor die Eingänge einen externen 6:1
> Teiler aufbaue,

Darüber solltest du eventuell noch mal ein paar Minuten nachdenken. Nimm 
mal Zettel und Bleistift und mal dir auf, was dann passiert. :)

von Chris D. (myfairtux) (Moderator) Benutzerseite


Lesenswert?

Hallo Matthias,

40 kHz sind kein Problem, das geht sogar mit vier Achsen gleichzeitig.

Ich habe damals eine Anzeige für Encoder mit einem 8515 und nur 16MHz 
gebaut, der gleichzeitig noch die Ansteuerung der 7-Seg-Anzeigen 
(flimmerfrei! ;-) und die Umrechung vornahm. Ach so: und RS-232-Ausgabe 
und Befehlsverarbeitung ging auch noch (alles mit gcc-avr)
Einachsig nahm das Teil sogar über 100kHz ohne Schrittverluste.

Mein Tipp dazu: pack das Einlesen der Encoder in eine kleine (!) 
Interruptroutine, das muss naturgemäß sehr flott erfolgen. Eventuell 
auch direkt interne Register zuweisen, damit nicht auf dem Stack 
rumgerödelt wird.
Die Anzahl der Rechts/Linksschritte speicherst Du in kleinen 
16-Bit-Integern, die dann außerhalb der ISR weiterverarbeitet und wieder 
genullt werden (achtung: atomare Operationen!).

Also, man kann ich 8kByte eine Menge unterbringen ;-)

Chris

von Mathias B. (matthiasbuerkle)


Lesenswert?

Das mit dem Interrupt hab ich mir schon überlegt und mal ausprobiert, 
aber in das Thema muss ich mich noch weiter einarbeiten.
Zu dem Tipp mit der 16-bit Integer: Ich muss insgesamt um die 2,4 * 10^6 
Impulse einlesen. Sprich, eine 16-bit Integer würde da gar nicht 
reichen. Kann ich da nicht einfach eine 32-bit Integer nehmen(global 
angelegt), die ich bei jeder Interrupt-Routine einfach mit 1 addiere:

z.B. so:   i_counter++;

Sollte für den Interrupt ja eigentlich schnell genug gehen nehm ich an.

Danke schon mal für eure zahlreichen Tips :-)

Matthias

von Chris D. (myfairtux) (Moderator) Benutzerseite


Lesenswert?

Die Idee mit den 16-Bit-Integern dient der Verkürzung der ISR-Laufzeit.

Der "Trick" ist, dass man die 16-Bit-Zahlen außerhalb des Interrupts 
verarbeitet und dort diese auf 0 setzt.
Damit können diese Werte nicht überlaufen - vorausgesetzt, man liest die 
Register genügend schnell aus.
Bei 16 Bit (-32768 bis +32767) hättest Du bei 40 kHz etwa 0,8 Sekunden, 
um zumindest einmal die Werte auszulesen und die Zähler zurückzusetzen.

Außerhalb kannst Du natürlich mit 32 Bit arbeiten (ich hatte damals 
sogar long long - geht also alles :-)

Ich musste das damals so machen, weil bei vier Achsen und 32-Bit-Werten 
die Laufzeit zu groß geworden wäre.
Wenn Du nur eine Achse auslesen musst, kannst Du vermutlich auch mit 32 
Bit und Deiner beschriebenen Addition arbeiten.

Chris

P.S.: Ich konnte damals bei 16MHz eine "Interruptfrequenz" von über 
120kHz bei einer Achse nehmen, das waren irgendwas um die 120 Takte für 
den gesamten Zyklus - und die darf man sich ja nicht voll genehmigen, 
weil außerhalb der ISR ja auch noch etwas passieren soll :-)

von Mathias B. (matthiasbuerkle)


Lesenswert?

Also wenn ich dich richtig verstanden hab, könnte ich z.B. in der 
Interruptroutine eine 16bit-Integer einfach aufzählen lassen. Und im 
Hauptprogramm dann den Wert der 16bit zu dem Wert der 32bit-Integer 
addieren und eine 0 in die 16bit-Integer schreiben.

Das mit der einen Achse ist nur im Moment beim Testaufbau. Die Maschine 
hat nacher 4 Achsen, die (im extremsten Fall) alle gleichzeitig bewegt 
werden sollen. Ich muss jetzt mal schauen, wie alles mit einer Achse 
funktioniert. Sobald diese eine läuft, kopier ich das Programm einfach 
für die anderen Achsen. Sollten da Probleme mit der Laufzeit auftreten, 
dachte ich mir, dass ich die Achsen auf mehrere Controller aufteile.

Aber ich bleib erstmal bei der einen Achse, jeder fangt ja mal klein an. 
;-)

Matthias

von Chris D. (myfairtux) (Moderator) Benutzerseite


Lesenswert?

Matthias Bürkle wrote:
> Also wenn ich dich richtig verstanden hab, könnte ich z.B. in der
> Interruptroutine eine 16bit-Integer einfach aufzählen lassen. Und im
> Hauptprogramm dann den Wert der 16bit zu dem Wert der 32bit-Integer
> addieren und eine 0 in die 16bit-Integer schreiben.

Genau :-)

Du musst nur darauf achten, während der Addition den Interrupt zu 
verhindern, weil der sonst "dazwischenhauen" könnte, also etwa so:

cli ();
counter32 += (signed long) isr16_counter;
isr16_counter = 0;
sei ();

> Das mit der einen Achse ist nur im Moment beim Testaufbau. Die Maschine
> hat nacher 4 Achsen, die (im extremsten Fall) alle gleichzeitig bewegt
> werden sollen. Ich muss jetzt mal schauen, wie alles mit einer Achse
> funktioniert. Sobald diese eine läuft, kopier ich das Programm einfach
> für die anderen Achsen. Sollten da Probleme mit der Laufzeit auftreten,
> dachte ich mir, dass ich die Achsen auf mehrere Controller aufteile.
>
> Aber ich bleib erstmal bei der einen Achse, jeder fangt ja mal klein an.
> ;-)

Eben - bis damals alles so lief, wie es sollte, vergingen auch einige 
Wochen. Wenn Du vier Controller einsetzen kannst, solltest Du das tun - 
die Arbeitszeit für die Optimierungen hast Du sofort wieder drin :-)

Chris.

von Mathias B. (matthiasbuerkle)


Lesenswert?

Hallo Chris,

ich hab jetzt mal probiert über den INT1 Eingang eine Interruptroutine 
auszuführen. Leider klappts nicht so wie ich will :-(.

Vielleicht kannst du mal schnell über den Code schauen. Er ist auf ein 
Minimum reduziert. (Wahrscheinlich schon zu wenig ;-)  ).

Hier ist der Code:
1
#include <stdlib.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
5
6
int8_t i=0;
7
8
9
ISR(INT1_vect)
10
{
11
   if(i=0)
12
   {
13
      i=1;
14
   }
15
   else
16
   {
17
      i=0;
18
   }
19
}
20
21
22
23
int main(void)
24
{
25
  DDRA |= (1<<PA5);         //PA5 als Ausgang deklarieren
26
   PORTA |= (1<<PA5);        //LED2 ausschalten
27
  
28
  GIMSK = 0b10000000;
29
  MCUCR = 0b00001000;
30
  
31
  sei();
32
  
33
  while(1)
34
  {
35
     if(i=1)
36
     {
37
        PORTA &= ~(1<<PA5);
38
       }
39
     else
40
     {
41
        PORTA |= (1<<PA5);
42
     }
43
  }
44
   
45
   return(0);
46
}

Ich wäre dir sehr dankbar, wenn du mir sagen könntest was ich falsch 
mache.

Matze

von Chris D. (myfairtux) (Moderator) Benutzerseite


Lesenswert?

Also, ohne das Programm getestet zu haben:

Die Bedingung

 if (i=0)

ist immer wahr, ich würde es auf jeden Fall mal mit

 if (i==0)

versuchen ;-)

Beliebter Fehler - eigentlich sollte der GCC da eine Warnung rauswerfen 
(kommt vermutlich auf die eingestellte Warnstufe an).

Chris

von Mathias B. (matthiasbuerkle)


Lesenswert?

Voll peinlich :-|. Anfängerfehler!!

Naja, hab ich korrigiert, aber geht immer noch nicht.
Die LED bleibt nach dem Einschalten der Stromversorgung aus und reagiert 
auch nicht auf die Tastendrücke.

Danke schon mal

Matze

von conradRP6 (Gast)


Lesenswert?

Du soll auch noch die Interrupts activieren. Bei mega32 :

// Initialize External interrupts - all disabled:
  MCUCR = (1 << ISC11) | (1 << ISC10) | (1 << ISC01) | (1 << ISC00);
  GICR = (0 << INT2) | (0 << INT1) | (0 << INT0);
  MCUCSR = (0 << ISC2);

Jetzt wird der Interrupt bei jeden niveau Wechsel activiert.

von Mathias B. (matthiasbuerkle)


Lesenswert?

Ok, aber hab ich das nicht schon mit den Befehlen "GIMSK = 0b10000000" 
und "MCUCR = 0b00001000;" gemacht?

Ich hab deinen Code mal bei mir eingesetzt, aber leider geht immer noch 
nix. Irgentwie blick das mit dem Interrupt nicht wirklich.

Ich hab nochmal den abgeänderten Code, vielleicht sieht ja jemand den 
Bug:
1
#include <stdlib.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
5
6
int8_t i;
7
8
9
ISR(INT1_vect)
10
{
11
   if(i==0)
12
   {
13
      i=1;
14
   }
15
   else
16
   {
17
      i=0;
18
   }
19
}
20
21
22
23
int main(void)
24
{
25
  DDRA  |= (1<<PA5);         //PA5 als Ausgang deklarieren
26
  PORTA |= (1<<PA5);        //LED2 ausschalten
27
28
29
  i=0;
30
  
31
  while(1)
32
  {
33
     MCUCR  = (1 << ISC11) | (1 << ISC10) | (1 << ISC01) | (1 << ISC00);
34
     GICR   = (0 << INT2)  | (0 << INT1)  | (0 << INT0);
35
     MCUCSR = (0 << ISC2);
36
   
37
   
38
  
39
     sei();
40
   
41
     if(i==1)
42
     {
43
        PORTA &= ~(1<<PA5);
44
     }
45
     else
46
     {
47
        PORTA |= (1<<PA5);
48
     }
49
   
50
  }
51
   
52
   return(0);
53
}

Matze

von Karl H. (kbuchegg)


Lesenswert?

Matthias Bürkle wrote:

> Ich hab nochmal den abgeänderten Code, vielleicht sieht ja jemand den
> Bug:

Ich hab jetzt die Registerinitialisierungen nicht mit dem Datenblatt 
verglichen, aber du musst i auf jeden Fall mal volatile machen (auch ein 
beliebter Anfängerfehler)

>
1
> #include <stdlib.h>
2
> #include <avr/io.h>
3
> #include <avr/interrupt.h>
4
> 
5
> 
6
> volatile int8_t i;
7
> ...

Wenns danach immer noch nicht tut, dann schiebst du die LED 
ein/ausschalterei mal in die ISR hinein. Wenn sich danach immer noch 
nichts tut, dann stimmt an deiner Registerkonfigurierung was nicht.

Oh. Und lass um Himmels willen die Konfiguration in Ruhe. Das macht man 
einmal(!) und danach lässt man sie in Ruhe (es sei denn man muss 
umkonfiguerieren). Aber es ist im schlimmsten Fall kontraproduktiv, wenn 
du innerhalb der while Schleife ständig an den Konfigurationsregistern 
herumfummelst (selbiges für den sei)

von Mathias B. (matthiasbuerkle)


Lesenswert?

Hallo Karl Heinz,
danke für deine Tipps, aber es geht immer noch nicht. Ich hab jetzt das 
"volatile" vor "int8_t i" gesetzt, die Ein- & Ausschalterei in die 
Routine geschrieben und die Konfiguration und das sei() vor die 
while-Schleife gezogen. Aber es geht immer noch nicht.

Trotzdem vielen Dank
Matze

von Karl H. (kbuchegg)


Lesenswert?

Matthias Bürkle wrote:
> Hallo Karl Heinz,
> danke für deine Tipps, aber es geht immer noch nicht. Ich hab jetzt das
> "volatile" vor "int8_t i" gesetzt, die Ein- & Ausschalterei in die
> Routine geschrieben und die Konfiguration und das sei() vor die
> while-Schleife gezogen. Aber es geht immer noch nicht.

Noch ein Tipp:
Bevor du dich in die Arbeit stürzt und deinen Code hier gross 
beschreibst, geh in deinen Programmier-Editor, selektier den ganzen 
Code, machen einen Copy, wechsle hier ins Fenster und mach einen paste. 
Wenn du freundlich bist, setzt du noch ein [ C ] davor und ein [ / C ] 
dahinter (jeweils ohne die Leerzeichen).
Das hat viele Vorteile:
* zum einen sehen wir hier was du wirklich programmiert hast
* zum anderen ist es für dich viel weniger Arbeit
* und zu guter letzt läufst du nicht Gefahr einen Tippfehler zu machen

Sollst du das immer machen? Ja, immer!

Zu deinem Problem:
Dann wird wohl bei der Initialisierung der Interrupts was nicht stimmen. 
Also: Nochmal Datenblatt rausholen und alle gesetzten Bits vergleichen.
Danach nochmal Datenblatt studieren, ob was vergessen wurde.

von Karl H. (kbuchegg)


Lesenswert?

1
     GICR   = (0 << INT2)  | (0 << INT1)  | (0 << INT0);
2
     MCUCSR = (0 << ISC2);

Was denkst du, was das wohl machen wird?

von Mathias B. (matthiasbuerkle)


Lesenswert?

In dem Register GICR aktiviere bzw. deaktiviere ich die einzelnen 
Interrupts. So hab ichs zumindest verstanden.

Das MCUCSR kenn ich nicht, ich kenn nur MCUCR mit dem man einstellt, 
welche Ereignisse das Interrupt auslösen.

von Christian R. (supachris)


Lesenswert?

Ich glaube der Herr Moderator meint eher die vielen Nullen da, die da 
bestimmt nicht hinsollen ;)

von Mathias B. (matthiasbuerkle)


Lesenswert?

OK, steht ja auch drüber, dass damit alle "disabled" sind.

>// Initialize External interrupts - all disabled:
>  MCUCR = (1 << ISC11) | (1 << ISC10) | (1 << ISC01) | (1 << ISC00);
>  GICR = (0 << INT2) | (0 << INT1) | (0 << INT0);
>  MCUCSR = (0 << ISC2);

Aber wenn ich jetzt den INT1=1, ISC11=1 und ISC10=0 (für die Auswertung 
der negativen Flanke), macht der µC immer noch nix :-(.

von Mathias B. (matthiasbuerkle)


Lesenswert?

Hallo, ich bin mal wieder am probieren.

Ich bin endlich weitergekommen und wollte nur den momentanen Code online 
stellen, mit dem das bis jetzt ganz zuverlässigt klappt.
Ich hab aber noch vor die Auswertung zu überprüfen, in dem ich einen 
Absolutdrehgeber(mit fertiger Auswertung) starr mit dem Inkrementalgeber 
verbinde und die Anzeigen vergleiche.

Sobald ich da was weis, meld ich mich wieder.

Aber hier erstmal mein Code:
1
#include <avr\io.h>
2
#include <avr\interrupt.h>
3
#include "lcd.h"
4
5
6
volatile int8_t interrupt_i;
7
8
SIGNAL(INT0_vect)
9
{
10
  if(PIND & (1<<PD3))
11
  {
12
    interrupt_i++;
13
  }
14
}
15
16
SIGNAL(INT1_vect)
17
{
18
  if(PIND & (1<<PD2))
19
  {
20
    interrupt_i--;
21
  }
22
}
23
24
25
int main(void)
26
{
27
  int16_t wert1=0;
28
  int16_t wert2=0;
29
  char ausgabe1[8];
30
31
  
32
  DDRD  &= ~(1<<PD2);
33
  DDRD  &= ~(1<<PD3);
34
  PORTD |=  (1<<PD2);
35
  PORTD |=  (1<<PD3);
36
  
37
  MCUCR = (1 << ISC11) | (1 << ISC10) | (1 << ISC01) | (1 << ISC00);
38
  GICR = (0 << INT2) | (1 << INT1) | (1 << INT0);
39
  MCUCSR = (1 << ISC2);
40
  
41
  lcd_init(LCD_DISP_ON);
42
  lcd_clrscr();
43
  lcd_gotoxy(0,0);
44
  lcd_puts("Wert:");
45
  lcd_gotoxy(7,0);
46
  lcd_puts(ausgabe1);
47
48
  sei();
49
  
50
  while(1)
51
  {
52
    wert1 = wert1 + interrupt_i;
53
    interrupt_i=0;
54
    
55
    itoa(wert1,ausgabe1,10);
56
    if(wert2 != wert1)
57
    {
58
      lcd_gotoxy(7,0);
59
      lcd_puts("          ");
60
      lcd_gotoxy(7,0);
61
      lcd_puts(ausgabe1);
62
      wert2 = wert1;
63
    }
64
  }
65
}

Bis dann
Matze

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.