Forum: Mikrocontroller und Digitale Elektronik Impulse mit Interrupt zählen


von Johannes (Gast)


Angehängte Dateien:

Lesenswert?

Hi ,
ich habe mir eine Schaltung aufgebaut, in der ein channel eines 
Inkrementalgebers auf den Int0 Eingang eines Atmega32 geht.
Beim Drehen soll der uC nun die Impulse zählen und ab einer gewissen 
Zahl
(in diesem Fall 1350) ein Relais schalten. Bei 1500 Impulsen soll der 
counter auf 0 zurück gesetzt werden.

So weit funktioniert das Programm auch, jedoch habe ich noch ein 
Problem.
Wenn ich am Motor Drehe (Übersetzung 1:15) dreht sich die Scheibe des 
Inkrementalgebers also 15 mal schneller. Auf dieser Scheibe sind 500 
Löcher.
Bei einer Drehzahl von 200 u/min würde ich also 1.500.000 Impulse die 
Minute eingehen. Der uC läuft auf 16MHz.

Wenn ich jetzt aber schon etwas schneller mit der hand diesen Motor 
drehe kommt der Punkt wo das Relais geschaltet wird sehr unregelmäßig.
Ist es möglich das er uC das nicht verarbeiten kann ?

Und wie könnte ich dieses Problem beheben?

Gruß

: Verschoben durch Moderator
von Johannes (Gast)


Lesenswert?

Die Werte von der Variable Count im Programm sind noch auf Testwerte 
gesetzt

von g457 (Gast)


Lesenswert?

> Ist es möglich das er uC das nicht verarbeiten kann ?

Ja.

> Und wie könnte ich dieses Problem beheben?

Counter nehmen statt Interrupt. Selbst dann wirds aber knapp bei einem 
Taktverhältnis von 16:1.5 (müsstest mal ins Datenplatt schauen ob das 
reicht). Wenn das noch nicht langt einen externen Counter davorhängen.

von s.g. (Gast)


Lesenswert?

Ich kenne zwar den Atmeg32 nicht, aber bei Dir kommt alle 40µs ein 
Impuls an. Bei 16MHz beträgt die Befehlsabarbeitungszeit 62,5ns (?). 
Damit hast Du 640 Befehlszeiten, um einen Impuls zu verarbeiten.
Wenn meine Einschätzung zum Atmeg32 richtig ist, dann sollte das locker 
reichen.

von Amateur (Gast)


Lesenswert?

Ich würde mal mit der Optimierung (-o?) rumspielen.
Das unnötige &= aus der Unterbrechungsroutine herausnehmen.
Die ISR-Struktur in:
if (<80)
...
else {
 if(<=150)
 ...
 else
 ...
}
ändern.

Eine weitere Möglichkeit ist: Auslagern und in assembler neu schreiben.

Zählen kommt wohl nur infrage, wenn die Hardware noch nicht steht.

von Peter II (Gast)


Lesenswert?

Amateur schrieb:
> Eine weitere Möglichkeit ist: Auslagern und in assembler neu schreiben.

das bringt hier wenig.

lass doch mal das volatile von count weg, es ist hier nicht notwendig 
und kostet sehr viel rechenzeit weil jedesmal die Daten aus dem Ram neu 
gelesen und geschrieben werden. Das sollte die geschwindigkeit der ISR 
verdoppeln.

von Erich (Gast)


Lesenswert?

Du solltest dir das Eingangssignal mit einem Scope betrachen.
Evtl. sind da Störungen drauf bzw. zu kurze Nadeln.
Ggf. Schmitttrigger wie 74HC132 dazwischenschalten.

Arbeitet der INT auf steigende, fallende oder beiden Flanken?
In deinem Programm fehlen sämtliche Kommentare.

Der Rat mit "Assembler neu schreiben" ist natürlich Quatsch.
Optimierung kontrollieren; ggf. Listingfile ansehen.

Gruss

von Amateur (Gast)


Lesenswert?

>Der Rat mit "Assembler neu schreiben" ist natürlich Quatsch.
>Optimierung kontrollieren; ggf. Listingfile ansehen.

Ich habe mir das .lss angesehen.
Da kann man schon was optimieren.
Vor allem, wenn man, lt. s.g., nur 640 Taktzyklen, für den ganzen Rest, 
zur Verfügung hat.

von Uwe (Gast)


Lesenswert?

Pro Puls hast der µC 1/1500000=666ns Zeit den interupt aufzurufen, 
Register zu sichern, zu Zählen, Register zurückzuschreiben. 666ns sind 
ca. 10 Taktzyklen. Sagen wir 2 um in den Interupt zu springen,2 zum 
Register sichern, 2 zum Zählen, 2 für Register zurückschreiben, 2 um 
zurückzuspringen. Dann sind wir bei 10 Taken und der µC wäre voll 
ausgelastet. Er verhaspelt sich. Du mußt in Harware Zählen oder nen 
schnellern µC nehmen.

von Uwe (Gast)


Lesenswert?

Nimm doch nen STM32F4 der hat nen Inkrementalencoder in Hardware 
eingebaut.
Das Discovery Board kostet 14€.

von s.g. (Gast)


Lesenswert?

Uwe schrieb:
> µC 1/1500000=666ns

???

1 Minute hat in der Regel 60 Sekunden, das solltest Du berücksichtigen.

von Mo (Gast)


Lesenswert?

Benutze einen Hardware Counter und einen Counter Compare Interrupt.

von Mo (Gast)


Lesenswert?

Ich meine natürlich die die du eh im Atmega eingebaut hast.

von Uwe (Gast)


Lesenswert?

> 1 Minute hat in der Regel 60 Sekunden, das solltest Du berücksichtigen
Ups naja ist halt Freitag ... Gehüähn schon offline ...

von Thomas E. (thomase)


Lesenswert?

Uwe schrieb:
> Nimm doch nen STM32F4 der hat nen Inkrementalencoder in Hardware
> eingebaut.
> Das Discovery Board kostet 14€.

Für so einen Piffelkram den Timer zu nehmen, kostet, ausser ein bisschen 
Nachdenken, gar nichts.

Aber schön, daß sich die STM32-Jünger auch mal wieder melden.


> (in diesem Fall 1350) ein Relais schalten. Bei 1500 Impulsen soll der
> counter auf 0 zurück gesetzt werden.
1
//Atmega48
2
TCCR1B |= (1 << WGM12) | (1 << CS12) | (1 << CS11) | (1 << CS10); //CTC, ext.
3
OCR1A = 1500;
4
OCR1B = 1350;
5
TIMSK1 |= (1 << OCIE1B);
6
//...
7
8
ISR{TIMER1_COMPB_vect)
9
{
10
  //Schalte Relais
11
}

mfg.

von Johannes (Gast)


Lesenswert?

@Peter II und Amateur:
Habe das Programm ein wenig optimiert, der Fehler tritt jedoch weiter 
auf

@Uwe
Der Interrupt arbeitet mit steigender Flanke, und ein Scope habe ich 
leider gerade nicht im equipment ;)

@Mo
Könntest du mir das ein wenig genauer beschreiben? Wäre dir sehr dankbar 
:)

Danke für eure schnellen Antworten, ich bleib dran

von Johannes (Gast)


Lesenswert?

@ Thomas Eckmann

Ich denke das dies der Part des Timers ist?!
Beszieht sich dieser auch auf die Ingterrupt Ports?
Zudem OCR1A und OCR1B, sind dies die Grenzen für den Bereich wo 
geschaltet werden soll?
Hatte mit Timern leider noch nichts wirklich am Hut.


> //Atmega48
> TCCR1B |= (1 << WGM12) | (1 << CS12) | (1 << CS11) | (1 << CS10); //CTC,
> ext.
> OCR1A = 1500;
> OCR1B = 1350;
> TIMSK1 |= (1 << OCIE1B);
> //...
>
> ISR{TIMER1_COMPB_vect)
> {
>   //Schalte Relais
> }


Gruß

von Amateur (Gast)


Lesenswert?

Wenn es ein zeitliches Problem ist, so kannst Du dies "mechanisch" 
herausfinden.

Einfach mal, einige Zeit, gemächlich drehen/pulsen und alternativ volle 
Pulle.

Im letzten Falle müsste sich ein Absturz reproduzieren lassen. Im 
ersten, müsste sich dein Port wie gewünscht verhalten – auch über 
längere Zeit hinweg.

von Peter II (Gast)


Lesenswert?

Johannes schrieb:
> @Peter II und Amateur:
> Habe das Programm ein wenig optimiert, der Fehler tritt jedoch weiter
> auf

zeigt es uns doch bitte mal.

von Clemens S. (zoggl)


Lesenswert?

mal eins schuss ins blaue:
dein code stimmt und deine HW passt.

schaltet dein relais bei 1350 ein und bei 1500 wieder aus?

=> kann es sein, dass dein armes relais bei 50-60U/min einfach nicht 
mehr nach kommt? das muss dann immerhin in 50-60ms ein und aus schalten. 
da ist es gut möglich dass schaltspiele nicht ausgeführt werden.

sg

von Thomas E. (thomase)


Lesenswert?

Johannes schrieb:
> @ Thomas Eckmann
>
> Ich denke das dies der Part des Timers ist?!
> Beszieht sich dieser auch auf die Ingterrupt Ports?
> Zudem OCR1A und OCR1B, sind dies die Grenzen für den Bereich wo
> geschaltet werden soll?
> Hatte mit Timern leider noch nichts wirklich am Hut.
>
>
>> //Atmega48
>> TCCR1B |= (1 << WGM12) | (1 << CS12) | (1 << CS11) | (1 << CS10); //CTC,
>> ext.
>> OCR1A = 1500;
>> OCR1B = 1350;
>> TIMSK1 |= (1 << OCIE1B);
>> //...
>>
>> ISR{TIMER1_COMPB_vect)
>> {
>>   //Schalte Relais
>> }
>
>
> Gruß

Im CTC-Mode zählt der Timer bis zum in OCR1A eingestellten Wert, geht 
auf 0 und fängt wieder von vorne an.
> Bei 1500 Impulsen soll der counter auf 0 zurück gesetzt werden.

Vorher löst er bei Erreichen von OCR1B einen Interrupt aus, in dem man 
das Relais schalten kann.
> (in diesem Fall 1350) ein Relais schalten.

Das einzige, was der Controller in Software zu erledigen hat, ist in der 
ISR einen Port zu schalten. Den Rest erledigt der Timer in Hardware. 
Währenddessen dreht die CPU Däumchen.

Der Zähleingang ist natürlich nicht mehr INT0 sondern T1. Falls beim 
Erreichen von 1500 noch etwas gemacht werden soll, kann man hierzu den 
COMPA-Interrupt zusätzlich einschalten.

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Die_Timer_und_Z%C3%A4hler_des_AVR

mfg.

: Bearbeitet durch User
von Johannes (Gast)


Angehängte Dateien:

Lesenswert?

So ich habe mein Prgramm nun etwas umgeschrieben. Eine Funktion ist 
leider noch nicht gegeben.

Zum testen drehe ich den motor am Getriebe mit der Hand, deswegen sollte 
dieser Bereich eigentlich passen. Habe ihn jetzt aber ein wenig 
angepasst bzw ein wenig vergößert.

von Thomas E. (thomase)


Lesenswert?

count muss volatile sein.
Da nur bis 151 gezählt wird, reicht auch uint8_t.

mfg.

: Bearbeitet durch User
von Johannes (Gast)


Angehängte Dateien:

Lesenswert?

Also erst mal nochmals vielen Dank für eure Hilfe.

@Thomas

Habe das Programm nun noch mal verändert. Den Bereich erweitert und 
trotzdem passiert nichts.

In einer Zeile schriebst du
>> TIMSK1 |= (1 << OCIE1B);

jedoch sagt mir mein AtmelStudio das es nur TIMSK kennt, war dies 
unbeabsichtigt?

Der Ausgang des Inkrementalgebers hängt nun an PORTB 2(also T1)

Aber warum zählt der Timer nur bis 151?

Gruß

von Peter II (Gast)


Lesenswert?

Thomas Eckmann schrieb:
> count muss volatile sein.

und warum?

Es gibt keine! Sie wird nur ein der ISR verwendet.

von Thomas E. (thomase)


Lesenswert?

Johannes schrieb:
> Also erst mal nochmals vielen Dank für eure Hilfe.
>
> @Thomas
>
> Habe das Programm nun noch mal verändert. Den Bereich erweitert und
> trotzdem passiert nichts.
>
> In einer Zeile schriebst du
>>> TIMSK1 |= (1 << OCIE1B);
>
> jedoch sagt mir mein AtmelStudio das es nur TIMSK kennt, war dies
> unbeabsichtigt?
Das ist das Register vom Atmega48. Ich hatte keine Lust ins Datenblatt 
vom 32er zu gucken. Die 16-Bit-Timer haben aber die gleichen Funktionen.

> Der Ausgang des Inkrementalgebers hängt nun an PORTB 2(also T1)
Muss da noch ein Pullup gesetzt werden?

> Aber warum zählt der Timer nur bis 151?
Nicht der Timer. Deine Variable count. Die setzt du selbst bei >150, 
also 151, zurück. Dafür reicht uint8_t.

mfg.

von Johannes (Gast)


Angehängte Dateien:

Lesenswert?

Ein großes großes Ups. Die Variable Counter wird jetzt langsam 
überflüssig, weil durch den Timer ja schon der Bereich gegeben ist. Das 
heißt im ISR muss ich nur einen Ausgang schalten.....Ich seh den Wald 
vor lauter Bäumen nicht mehr.

Also müsste mein Prgramm in so weit nun besser sein. Noch kurz 
herausfinden wie man noch mal Pullups setzt und dann folgt ein weiterer 
Statusbericht

von Johannes (Gast)


Lesenswert?

Thomas Eckmann schrieb:
>> Der Ausgang des Inkrementalgebers hängt nun an PORTB 2(also T1)
> Muss da noch ein Pullup gesetzt werden?

Wie erkenne ich dies denn und wie schalte ich die Pullups ein ?

Grüße

von Shee2e (Gast)


Lesenswert?

Wenn der Pin als Eingang konfiguriert ist normaler weiße einfach auf das 
Ausgang eine 1 Schreiben, aber habe grad nicht geprüft wenn man den Pin 
als
Sonderfunktion benutzt.
Also am beispiel Port B 0. Beinchen:
1
PortB|=0x01;
MfG
Shee2e

von Karl H. (kbuchegg)


Lesenswert?

Johannes schrieb:
> Thomas Eckmann schrieb:
>>> Der Ausgang des Inkrementalgebers hängt nun an PORTB 2(also T1)
>> Muss da noch ein Pullup gesetzt werden?
>
> Wie erkenne ich dies denn und wie schalte ich die Pullups ein ?

?
Das solltest du aber schon wissen.

Brauchst du einen Pullup?`
Das hängt davon ab, wie dein Encoder verschaltet ist.

Wie schaltet man ihn ein:
Wenn der Pin auf Eingang konfiguriert ist, dann wird er durch Setzen 
einer 1 für diesen Pin im PORTx Register eingeschaltet.

AVR-Tutorial: IO-Grundlagen
AVR-GCC-Tutorial

: Bearbeitet durch User
von Thomas E. (thomase)


Lesenswert?

Aus deinem Code:
> DDRB = 0xe0;
Gehört irgendwie gar nicht dahin.
Gewöhn dir auch die hexadezimale Schreibweise schnell wieder ab. Und 
schreib das so: DDRB = (1 << DDB7) | (1 << DDB6) | (1 << DDB5);
Ist zwar mehr Schreibarbeit, aber man sieht auf einen Blick welche Bits 
gesetzt werden.

> PORTD |= (1<<PD5);
Der Port muss auf Ausgang geschaltet werden.

mfg.

von Johannes (Gast)


Angehängte Dateien:

Lesenswert?

Karl Heinz Buchegger schrieb:
> Johannes schrieb:
>> Thomas Eckmann schrieb:
>>>> Der Ausgang des Inkrementalgebers hängt nun an PORTB 2(also T1)
>>> Muss da noch ein Pullup gesetzt werden?
>>
>> Wie erkenne ich dies denn und wie schalte ich die Pullups ein ?
>
> ?
> Das solltest du aber schon wissen.
>
> Brauchst du einen Pullup?`
> Das hängt davon ab, wie dein Encoder verschaltet ist.
>
> Wie schaltet man ihn ein:
> Wenn der Pin auf Eingang konfiguriert ist, dann wird er durch Setzen
> einer 1 für diesen Pin im PORTx Register eingeschaltet.
>
> AVR-Tutorial: IO-Grundlagen
> AVR-GCC-Tutorial

Ja das mit den Pullups ist noch Neuland für mich, aber danke für die 
Hilfe.

Thomas Eckmann schrieb:
> Aus deinem Code:
>> DDRB = 0xe0;
> Gehört irgendwie gar nicht dahin.
> Gewöhn dir auch die hexadezimale Schreibweise schnell wieder ab. Und
> schreib das so: DDRB = (1 << DDB7) | (1 << DDB6) | (1 << DDB5);
> Ist zwar mehr Schreibarbeit, aber man sieht auf einen Blick welche Bits
> gesetzt werden.
>
>> PORTD |= (1<<PD5);
> Der Port muss auf Ausgang geschaltet werden.

Ja da hatte ich noch einen Fehler. Habe es korrigiert und Tatsache:
Bei 350 Impulsen schaltet das Relais. Jedoch schaltet es sich nicht mehr 
aus. Ich dachte beim erreichen von OCR1B wird der ISR quasi wieder 
deaktiviert?!

Oder muss ich da noch etwas hinzufügen?

gruß

von c-hater (Gast)


Lesenswert?

Johannes schrieb:

> Bei einer Drehzahl von 200 u/min würde ich also 1.500.000 Impulse die
> Minute eingehen. Der uC läuft auf 16MHz.
>
> Wenn ich jetzt aber schon etwas schneller mit der hand diesen Motor
> drehe kommt der Punkt wo das Relais geschaltet wird sehr unregelmäßig.
> Ist es möglich das er uC das nicht verarbeiten kann ?

Natürlich. 1.5E6*11T=16.5

D.h.: selbst bei optimaler Umsetzung in Assembler (ISR direkt in der 
Vektortabelle, Zahlregister und Backup-Register für Flags reserviert, 
keine konkurrierenden Interrupts, kein cli()..sei()-Orgien) reicht es 
nichtmal für die 1,5MHz Interruptrate, geschweige denn für noch mehr.

Ein Interupt-Frame dauert immer mindestens acht Takte. Vier Takte, um 
die Rücksprungadresse auf den Stack zu sichern und weitere vier Takte, 
um den Rücksprung am Ende der ISR auszuführen. Die optimale ISR müßte 
direkt in der Vektortabelle liegen und sähe so aus:

vektor:          ;[4]
 in BACKUP,SREG  ; 1
 inc COUNT       ; 1
 out SREG,BACKUP ; 1
 reti            ; 4
                 ;--
                 ;11

Daher die "11T" aus der Rechnung von oben. Die Gleichung geht also 
frühestens bei 16.5MHz Takt auf. Dann ist die MCU aber immer noch 
vollständig durch die ISR ausgelastet, kommt also überhaupt nicht dazu, 
auch mal was in main() zu machen.

Im Übrigen ist es sowieso völlig bescheuert, so hohe Frequenzen mit 
einer ISR zählen zu wollen. Dafür gibt es Timer (die eigentlich ZÄHLER 
sind).

von s.g. (Gast)


Lesenswert?

c-hater schrieb:
> Natürlich. 1.5E6*11T=16.5

???

1 Minute hat in der Regel 60 Sekunden, das solltest Du berücksichtigen.

von Johannes (Gast)


Lesenswert?

c-hater schrieb:
> Im Übrigen ist es sowieso völlig bescheuert, so hohe Frequenzen mit
> einer ISR zählen zu wollen. Dafür gibt es Timer (die eigentlich ZÄHLER
> sind).


Danke für die Rechnung erstmal.
Aber ich glaube das ich nicht mehr über den ISR zähle, sondern bereits 
über einen Timer, wie man in meinem letzten Programm sehen kann.

von Karl H. (kbuchegg)


Lesenswert?

Johannes schrieb:

> Ja da hatte ich noch einen Fehler. Habe es korrigiert und Tatsache:
> Bei 350 Impulsen schaltet das Relais. Jedoch schaltet es sich nicht mehr
> aus. Ich dachte beim erreichen von OCR1B wird der ISR quasi wieder
> deaktiviert?!


Wieso soll der deaktiviert werden?

Der Portpin schaltet auf 1, sobald du
1
  PORTD |= (1<<PD5);

ausführen lässt.
Wenn der schon auf 1 ist, dann bewirkt das nicht. Denn wenn du am 
Lichtschalter auf "ein" drückst und das Licht schon eingeschaltet ist, 
passiert nichts weiter.

Wenn das Licht wieder aus gehen soll, dann musst du auf 'Aus' drücken.
1
  PORTD &= ~( 1 << PD5 );


Oder aber, wenn du ausdrücken willst "umschalten", dann eben
1
  PORTD ^= (1<<PD5);
eine XOR-Operation dafür einsetzen. Dann wechselt der Pin von 1 auf 0 
bzw. von 0 auf 1, jedesmal wenn diese Operation durchgeführt wird.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

>> Der Port muss auf Ausgang geschaltet werden.
> Ja da hatte ich noch einen Fehler. Habe es korrigiert

Ja. An der blödest möglichen Stelle.

Es reicht völlig aus, wenn du derartige Konfigurationen EINMAL, am 
Programmanfang machst.



Es hat schon seinen Grund, warum wir Anfänger erst mal durch die Mühle 
von 'Led gezielt einschalten', 'Led gezielt ausschalten', 'Led blinken 
lassen', 'Lauflichter' usw. durchschicken. Denn der ganz banale sichere 
Umgang mit Portpins ist Grundvoraussetzung für alles weitere.

: Bearbeitet durch User
von Johannes B. (aprendiz)


Lesenswert?

Karl Heinz Buchegger schrieb:
>>> Der Port muss auf Ausgang geschaltet werden.
>> Ja da hatte ich noch einen Fehler. Habe es korrigiert
>
> Ja. An der blödest möglichen Stelle.
>
> Es reicht völlig aus, wenn du derartige Konfigurationen EINMAL, am
> Programmanfang machst.
>
> Es hat schon seinen Grund, warum wir Anfänger erst mal durch die Mühle
> von 'Led gezielt einschalten', 'Led gezielt ausschalten', 'Led blinken
> lassen', 'Lauflichter' usw. durchschicken. Denn der ganz banale sichere
> Umgang mit Portpins ist Grundvoraussetzung für alles weitere.

Ja ich weiß, doch man lernt ja aus seinen Fehlern ;)

Die Grundschaltungen bin ich auch schon durchgegangen, da ich aber 
alleine arbeite und noch ein paar höher gesteckte Ziele verfolge kommt 
da wohl manches zu kurz.

Werde mich aber noch mal mit dem Umgang der Portpins beschäftigen, die 
Fehler geben einem ja doch zu denken.

Vielen Dank euch allen für die Hilfe

Gruß

von Amateur (Gast)


Lesenswert?

@Johannes

Zum Abschalten wirst Du wohl einen zweiten Interrupt auf 
TIMER1_COMPA_vect legen müssen. Aktivierung nicht vergessen.

"DDRD = (1<<PD5);"
In TIMER1_COMPB_vect bewirkt, dass PD5 zum Ausgang wird. Alle anderen 
Anschlüsse werden zu Eingängen - egal was eventuell an anderer Stelle 
"gesagt" wurde. Vorsicht also mit dem Gleichheitszeichen.
Warum machst Du das nicht einmalig während der Initialisierung, sondern 
ständig?

von c-hater (Gast)


Lesenswert?

s.g. schrieb:

> 1 Minute hat in der Regel 60 Sekunden, das solltest Du berücksichtigen.

Oops. Das habe ich tatsächlich nicht korrekt gelesen.

Wer zum Teufel gibt bei bei Problemen rund um die Rechenleistung auch 
irgendwas in min-1 an, damit kann man doch nicht ernsthaft rechnen? Das 
ist, als wenn ein handwerklicher Goldschmied in Tonnen rechnen würde, 
einfach nur idiotisch.

Aber OK, mit 60 mal mehr Zeit entspannt sich die Lage natürlich ganz 
erheblich. 25kHz Interruptrate sind zwar auch nicht ganz ohne, aber 
sogar in C gut beherrschbar.

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.